"But it works on my machine!" is probably the most repeated sentence in software development. A developer builds an app on a laptop running Python 3.11, and the server runs Python 3.8. A library is missing, an environment variable is different, and a deployment that should take minutes takes days.
Docker was built to end this problem. Since its launch in 2013, it has changed how software is built, shipped, and run. This post covers why Docker exists, what it gives you, and the core commands you'll use, each with a practical use case and its trade-offs.
Why Do We Need Docker?
Before containers, teams had two main choices for running applications:
- Bare-metal servers: each app runs directly on the OS. Dependencies conflict, and one misbehaving app can affect the others.
- Virtual machines (VMs): each app gets a full guest operating system. This isolates apps well, but every VM carries gigabytes of overhead and takes minutes to boot.
Docker offers a third option: containers. A container packages your application with everything it needs (code, runtime, libraries, system tools, configuration) and runs it as an isolated process on the host's kernel. There is no guest OS, so containers start in seconds and use a fraction of the resources.
The problems Docker solves:
- Environment inconsistency: the same image runs identically on a laptop, a CI server, and production.
- Dependency conflicts: Project A needs Node 14 and Project B needs Node 20? Run both in separate containers.
- Slow onboarding: a new developer runs one command instead of following a 40-step setup document.
- Inefficient resource use: you can run dozens of containers on hardware that would host only a few VMs.
- Painful deployments: shipping an image is simpler and more predictable than installing software on servers.
Core Concepts in 60 Seconds
- Image: a read-only template containing your app and its dependencies. Think of it as a blueprint.
- Container: a running instance of an image. You can create many containers from one image.
- Dockerfile: a text file with instructions for building an image.
- Registry: a storage service for images. Docker Hub is the most popular; AWS ECR, GitHub Container Registry, and others also exist.
- Volume: persistent storage that outlives a container.
- Network: a virtual network that lets containers talk to each other.
Advantages of Using Docker
- Portability. "Build once, run anywhere" is largely true. If a machine runs Docker, it can run your container.
- Consistency across environments. Development, testing, and production use the same image, which removes a whole category of bugs.
- Lightweight and fast. Containers share the host kernel, so they start in milliseconds to seconds and use far less memory than VMs.
- Isolation. Each container has its own filesystem, processes, and network stack. A crash or compromise in one is contained.
- Scalability. Need ten more instances of your web server? Start ten more containers. Orchestrators like Kubernetes automate this.
- Version control for infrastructure. Images are tagged and versioned, so rolling back is as simple as running the previous tag.
- CI/CD friendliness. Pipelines can build an image once, test it, and promote that exact artifact to production.
- Huge ecosystem. Docker Hub hosts official images for PostgreSQL, Redis, Nginx, Node, Python, and thousands more. You can spin up a database in seconds without installing anything.
- Efficient use of layers. Images are built in cached layers, so rebuilds and downloads only process what changed.
Docker Commands with Use Cases, Pros, and Cons
Docker's CLI has well over a hundred subcommands and options, so no post can list literally every one. Below are the commands that cover nearly all real-world work, grouped by purpose.
1. Setup and Information
docker version shows client and server versions.
- Use case: verifying your install, or checking version compatibility when debugging CI issues.
- Pros: quick and confirms the daemon is reachable. Cons: purely informational.
docker info displays system-wide details: number of containers, storage driver, OS, resources.
- Use case: diagnosing why builds are slow or disk space is disappearing.
- Pros: rich diagnostics in one command. Cons: verbose output.
2. Working with Images
docker pull nginx:1.27 downloads an image from a registry.
- Use case: pre-fetching images on a server before a deployment window.
- Pros: pinning a tag gives repeatable results. Cons: omitting the tag pulls
latest, which can silently change and break things.
docker images (or docker image ls) lists local images.
- Use case: auditing which images occupy your disk.
- Pros: shows size and age at a glance. Cons: the list grows messy without regular cleanup.
docker build -t myapp:1.0 . builds an image from a Dockerfile in the current directory.
- Use case: packaging your application for testing or release.
- Pros: repeatable, cached builds. Cons: poorly ordered Dockerfiles cause slow builds and bloated images.
A minimal Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
docker tag myapp:1.0 myuser/myapp:1.0 gives an image an additional name or tag.
- Use case: preparing an image for a registry, or marking a tested build as
stable. - Pros: instant, no copying of data. Cons: tags are mutable, so someone can overwrite one.
docker push myuser/myapp:1.0 uploads an image to a registry.
- Use case: sharing a build with teammates or a deployment pipeline.
- Pros: central distribution point. Cons: public repositories can leak secrets baked into images.
docker login authenticates against a registry.
- Use case: accessing private images.
- Pros: enables private workflows. Cons: credentials are stored in plain config unless a credential helper is set up.
docker history myapp:1.0 shows the layers of an image and their sizes.
- Use case: finding which instruction made an image huge.
- Pros: great for optimization. Cons: multi-stage builds can hide some details.
docker rmi myapp:1.0 removes an image.
- Use case: freeing disk space.
- Pros: reclaims storage. Cons: fails if a container still uses the image.
docker save -o myapp.tar myapp:1.0 and docker load -i myapp.tar export and import images as tar files.
- Use case: moving images to an air-gapped server with no registry access.
- Pros: works offline. Cons: files are large and there's no layer deduplication across transfers.
3. Running Containers
docker run creates and starts a container. It's the most-used command.
docker run -d --name web -p 8080:80 nginx
-druns in the background,--namenames the container,-p host:containermaps ports.- Use case: launching a web server, database, or one-off tool in seconds.
- Pros: one command replaces a long install process. Cons: many flags make commands long and error-prone, which is why Compose exists.
Other useful flags: -e KEY=value (environment variables), -v (mount volumes), --rm (delete on exit), -it (interactive terminal), and --memory / --cpus (resource limits).
docker ps lists running containers; docker ps -a includes stopped ones.
- Use case: checking what's running, or finding a crashed container.
- Pros: fast status overview. Cons: output gets wide without
--format.
docker stop web gracefully stops a container (sends SIGTERM, then SIGKILL after a timeout).
- Use case: shutting down a service without corrupting data.
- Pros: graceful shutdown. Cons: slow if the app ignores SIGTERM.
docker start web and docker restart web restart existing containers.
- Use case: applying a config change or recovering from a hang.
- Pros: keeps the container's data and settings. Cons: a restart doesn't pick up a newer image. You need to recreate the container for that.
docker kill web stops a container immediately.
- Use case: a frozen container that ignores
stop. - Pros: guaranteed termination. Cons: risks data loss.
docker rm web deletes a stopped container; docker rm -f web force-deletes a running one.
- Use case: cleaning up test containers.
- Pros: frees resources. Cons: data in the container's writable layer is lost unless you used volumes.
docker pause / docker unpause freeze and resume all processes in a container.
- Use case: briefly taking a snapshot or freeing CPU without losing state.
- Pros: keeps memory state. Cons: rarely needed, and network connections may time out.
4. Inspecting and Debugging
docker logs -f --tail 100 web streams the last 100 log lines.
- Use case: debugging a crashing app.
- Pros: no need to enter the container. Cons: logs can fill the disk if you don't configure rotation.
docker exec -it web bash runs a command inside a running container.
- Use case: checking config files, testing connectivity, or running database migrations.
- Pros: very effective for troubleshooting. Cons: manual changes inside a container are lost when it's recreated, so don't treat it as your fix. Slim images may lack
bash(trysh).
docker inspect web returns detailed JSON about a container or image.
- Use case: finding a container's IP address, mounts, or environment variables.
- Pros: complete metadata. Cons: verbose, so you'll often filter with
--format.
docker stats shows live CPU, memory, and network usage.
- Use case: spotting a container that leaks memory.
- Pros: real-time visibility. Cons: not a replacement for proper monitoring tools.
docker top web lists processes running inside a container.
- Use case: confirming your app spawned the expected processes.
- Pros: lightweight. Cons: limited detail.
docker cp web:/app/log.txt ./log.txt copies files between container and host.
- Use case: pulling a crash dump or config file out of a container.
- Pros: simple. Cons: not suited for regular data sharing. Use volumes instead.
docker diff web shows files changed in a container since it started.
- Use case: discovering what an app writes to disk, so you can decide what to mount as a volume.
- Pros: reveals hidden state. Cons: output can be noisy.
5. Volumes (Persistent Data)
docker volume create dbdata creates a managed volume, and docker run -v dbdata:/var/lib/postgresql/data postgres mounts it.
- Use case: keeping database files after a container is deleted.
- Pros: data survives container replacement, and volumes are portable and managed by Docker. Cons: harder to browse than a host folder.
docker volume ls, docker volume inspect, and docker volume rm list, describe, and delete volumes.
- Use case: auditing and cleaning storage.
- Pros: clear management. Cons: deleting a volume permanently deletes its data.
Bind mounts (-v $(pwd):/app) map a host folder into a container.
- Use case: live code reloading during development.
- Pros: edits appear instantly. Cons: tied to the host's paths and permissions, and slower on macOS and Windows.
6. Networking
docker network create mynet creates a custom network, and docker run --network mynet ... attaches containers to it.
- Use case: letting your API container reach the database by name (
db:5432) instead of an IP. - Pros: built-in DNS and isolation between groups of containers. Cons: networking issues can be tricky to debug without inspecting.
docker network ls, docker network inspect, and docker network rm list, describe, and remove networks.
- Use case: finding which containers share a network.
- Pros: clear visibility. Cons: you can't remove a network while containers are attached.
7. Docker Compose (Multi-Container Apps)
A docker-compose.yml defines a whole stack:
services:
web:
build: .
ports: ["3000:3000"]
depends_on: [db]
db:
image: postgres:16
volumes: [dbdata:/var/lib/postgresql/data]
volumes:
dbdata:
docker compose up -d starts every service.
- Use case: launching an app, database, and cache with one command.
- Pros: declarative, shareable, and version-controlled. Cons: aimed at single-host setups, not large-scale production.
docker compose down stops and removes the stack (add -v to delete volumes too).
- Use case: resetting your dev environment.
- Pros: clean teardown. Cons:
-vwipes data.
docker compose logs, ps, build, and exec mirror the single-container commands but work at the stack level.
- Pros: consistent workflow. Cons:
depends_onwaits for startup, not readiness, so you may need health checks.
8. Cleanup and Maintenance
docker system df shows disk usage by images, containers, and volumes.
- Use case: investigating a full disk.
docker system prune removes stopped containers, unused networks, and dangling images. docker system prune -a --volumes is far more aggressive.
- Use case: reclaiming space on a busy CI server.
- Pros: frees gigabytes quickly. Cons: destructive. Read the prompt carefully, especially with
--volumes.
Targeted versions exist too: docker image prune, docker container prune, and docker volume prune.
9. Less Common but Handy
docker commit web myimage:snap creates an image from a container's current state.
- Use case: quick experiments or snapshots.
- Pros: fast. Cons: not reproducible. Prefer a Dockerfile for anything permanent.
docker events streams real-time daemon events.
- Use case: debugging automation that reacts to container start and stop.
docker rename old new renames a container.
- Use case: fixing a naming mistake without recreating it.
docker update --memory 512m web changes resource limits on a running container.
- Use case: throttling a runaway container without downtime.
docker context switches between Docker hosts (local, remote, cloud).
- Use case: managing a remote server from your laptop.
Real-World Use Cases
- Development environments: everyone runs the same stack, whatever their OS.
- Microservices: each service ships as its own container with its own dependencies.
- CI/CD pipelines: tests run in clean, disposable containers.
- Trying new tools: run
docker run -it python:3.13to experiment without installing anything. - Legacy app packaging: freeze an old app and its old dependencies in an image.
- Data science: share reproducible environments with exact library versions.
Honest Cons and Limitations of Docker
No tool is perfect, and Docker has trade-offs:
- Learning curve: networking, volumes, and image optimization take time to master.
- Weaker isolation than VMs: containers share the host kernel, so a kernel vulnerability affects all of them.
- Persistent data needs care: containers are ephemeral by design, and mismanaging volumes causes data loss.
- Security responsibility: running as root, using unverified images, and baking in secrets are common mistakes.
- Performance overhead on Mac/Windows: Docker runs inside a lightweight VM there, and file mounts can be slow.
- Not ideal for everything: GUI-heavy desktop apps and workloads needing special kernel features fit poorly.
- Orchestration complexity: production-scale deployments usually need Kubernetes, which is a further learning investment.
Best Practices to Remember
- Use small base images (
alpine,slim) and multi-stage builds. - Pin image versions instead of using
latest. - Order Dockerfile instructions from least to most frequently changed to maximize caching.
- Use a
.dockerignorefile to keep builds lean. - Run containers as a non-root user.
- Never store secrets in images. Use environment variables or a secrets manager.
- Add health checks and log rotation.
- Prune unused resources regularly.
Conclusion
Docker turned "it works on my machine" from a joke into a solved problem. By packaging applications with their dependencies into portable, isolated containers, it makes development faster, deployments safer, and infrastructure easier to reason about. Its commands follow a logical pattern: build images, run containers, inspect and debug them, persist data with volumes, connect them with networks, and orchestrate them with Compose.
Start small. Containerize one project, write a Dockerfile, and use docker run, logs, and exec until they feel natural. Then add Compose, volumes, and networks. Once Docker clicks, you'll wonder how you ever managed environments without it.

Comments
Post a Comment