Docker for Beginners: From Zero to Containerized (2026)
"It works on my machine" is the most quoted sentence in software engineering. Docker is the technology that finally retired it. By packaging an application together with its runtime, libraries, and configuration into a single portable unit, Docker lets the same artifact run identically on a developer's laptop, a CI runner, and a production cluster.
This guide is for developers who have heard of Docker, maybe even run docker run hello-world once, but never quite built their mental model of what's actually happening. We'll cover the core concepts, the five commands you absolutely need, how to write a good Dockerfile, multi-stage builds, and Docker Compose for multi-service apps.
1. The Four Concepts You Must Know
Docker's vocabulary is small. Internalize these four and the rest is just CLI flags.
Image
A read-only template. Think of it as a class — a blueprint that defines what a running app will look like. Images are built from a Dockerfile and stored in registries like Docker Hub, Amazon ECR, or GitHub Container Registry.
Container
A running instance of an image — the object to the image's class. A container is a lightweight, isolated process on the host that shares the kernel but has its own filesystem, network, and process namespace. You can start, stop, delete, and move containers freely.
Layer
Every instruction in a Dockerfile creates a layer. Docker uses a Union File System to stack these layers into a single image. Layers are content-addressed and cached: if the first three lines of your Dockerfile haven't changed, Docker reuses them on the next build instead of re-running those steps. This is why instruction order matters.
Registry
A repository for images. Docker Hub is the public default. Private options include Harbor, AWS ECR, Google Artifact Registry, and GitHub Container Registry. You push images to a registry and pull them from anywhere with network access.
2. Containers vs Virtual Machines
Containers and VMs solve overlapping problems, but they cut the cake differently. Here's the honest comparison.
| Dimension | Container | Virtual Machine |
|---|---|---|
| Startup time | Seconds (often < 1s) | Minutes |
| Image size | Megabytes to low GB | Tens of GB |
| Performance | Near-native | Hypervisor overhead (~3–10%) |
| Operating system | Shares the host kernel | Runs its own full guest OS |
| Isolation | Process-level (namespaces + cgroups) | Hardware-level (full VM) |
The trade-off: containers are smaller and faster, but VMs give you stronger isolation and the freedom to run a different OS (Linux VM on a Windows host, for example). For most application workloads, containers win on density and speed.
3. The Five Commands You Need on Day One
The Docker CLI has hundreds of subcommands. You'll use five of them 90% of the time.
Pull an image
docker pull nginx:latest
Downloads the official nginx image from Docker Hub. The :latest tag is the default; pin to a version in production (e.g. nginx:1.27).
List local images
docker images
Run a container
docker run -d -p 8080:80 --name mynginx nginx
Flags you'll use constantly:
-d— detached: run in the background-it— interactive TTY (for shells)--rm— remove the container when it exits-p 8080:80— map host port 8080 to container port 80-v /host/path:/container/path— mount a volume-e KEY=VALUE— set an environment variable--name mynginx— give it a human-readable name
List containers
docker ps -a
The -a flag shows stopped containers too — without it, you only see what's currently running. This is the single most common gotcha for newcomers.
Stop, remove, logs, exec
docker stop mynginx # graceful shutdown (SIGTERM, then SIGKILL)
docker rm mynginx # remove the stopped container
docker logs -f mynginx # follow log output (like tail -f)
docker exec -it mynginx sh # open a shell inside the running container
4. Writing a Dockerfile
A Dockerfile is a recipe for building an image. Each line is an instruction, executed top to bottom, with each result cached as a layer.
Common instructions, in build order:
FROM ubuntu:22.04— base image (every Dockerfile starts here)WORKDIR /app— set the working directory for subsequent commandsCOPY . .— copy files from host to image (prefer overADD)RUN apt-get update && apt-get install -y python3— run a build stepENV NODE_ENV=production— set environment variablesEXPOSE 3000— documentation only; doesn't actually publish the portCMD ["node", "server.js"]— the default command (JSON array form preferred)ENTRYPOINT ["docker-entrypoint.sh"]— the executable that always runs
Here's a complete, production-ready Dockerfile for a Node.js app:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Notice the order: package.json is copied before the rest of the source. That way, Docker caches the npm install layer and only re-runs it when dependencies change — not on every code edit.
5. Multi-Stage Builds
If you compile a Go, Rust, or Java binary, you don't want the entire build toolchain shipped to production. Multi-stage builds solve this by letting one stage produce artifacts that another stage copies into a slim final image.
# Stage 1: build
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Stage 2: runtime
FROM alpine:3.19
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]
The final image is ~15 MB of Alpine Linux plus a single binary. The Go compiler, source tree, and build cache are discarded with the builder stage. A typical naive Dockerfile for the same app would be 800+ MB.
6. Docker Compose: Multi-Service Made Simple
Real applications need more than one process: a web server, a database, maybe a cache, maybe a queue. Docker Compose lets you declare all of them in a single YAML file and bring them up together.
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
The two commands that run your world:
docker compose up -d # start everything in the background
docker compose down # stop and remove containers (keeps volumes)
The db-data named volume persists the database across restarts. Without it, your data would vanish every time you ran down.
7. Where Docker Shines in Real Work
Local dev environments. New team member runs docker compose up and gets a working database, cache, and search index without installing any of them natively. No more "did you remember to start Redis?"
CI/CD pipelines. GitHub Actions, GitLab CI, and CircleCI all support Docker natively. Build once, run anywhere — the test environment in CI matches your local container exactly.
Microservices. Each service ships as its own image with its own dependencies, deployed independently. No more "the auth service needs Python 3.10 but the billing service is on 3.8."
Cloud-native deployment. Kubernetes orchestrates containers at scale: rolling deploys, self-healing, autoscaling, service discovery. Docker images are the standard input format.
8. Three Common Pitfalls
Hardcoding secrets in the Dockerfile
Anything in a Dockerfile ends up in a layer that's downloadable by anyone who can pull the image. Use docker run -e, Docker secrets, or a secret manager. Never ENV API_KEY=sk_live_xxx.
Bloated base images
Start from alpine or distroless instead of ubuntu or debian when you can. A Node.js app on node:20-alpine is ~50 MB; on node:20 it's ~350 MB. For Go and Rust, scratch or distroless gives you a 10–20 MB final image with no shell, no package manager, and a much smaller attack surface.
Copying .git and node_modules
Put a .dockerignore file in the same directory as your Dockerfile. It works like .gitignore:
.git
node_modules
.env
*.log
dist
.DS_Store
This keeps build context small, speeds up builds, and prevents accidental leaks of credentials stored in .env files.
9. Try It Yourself
Ready to write your first Dockerfile? Grab any text editor and a terminal. The official Docker "Get Started" guide walks through the same workflow with a slightly larger sample app. We don't host a Docker playground on DevToolbox, but the JSON formatter comes in handy for checking your docker-compose.yml syntax before you run it.
Frequently Asked Questions
Docker vs Kubernetes — what's the relationship?
Docker builds and runs containers. Kubernetes orchestrates them at scale. Docker is the engine; K8s is the conductor. You use Docker to package an image, and Kubernetes to schedule hundreds or thousands of those images across a cluster, handle rolling updates, restart failed pods, balance traffic, and discover services. You can run Docker without Kubernetes (most apps do), and Kubernetes without Docker (it supports containerd and CRI-O too).
Why does my container exit immediately after starting?
The main process inside the container finished. Docker only keeps a container alive as long as PID 1 is running — if that process exits (because it crashed, finished a batch job, or the wrong command was specified), the container stops. Run docker logs <name> to see what the process printed. For interactive debugging, override the command: docker run -it <image> sh.
How do I shrink my image size?
Three levers, in order of impact: (1) multi-stage builds to throw away build dependencies; (2) smaller base images — alpine for general use, distroless for compiled languages, scratch for static binaries; (3) clean package-manager caches in the same RUN step that installs them, e.g. apt-get install -y pkg && rm -rf /var/lib/apt/lists/*.
How do I persist container data?
Use volumes — Docker-managed directories stored in /var/lib/docker/volumes/ on the host. They're decoupled from the container lifecycle, so docker rm doesn't delete them. Bind mounts link a specific host path into the container (useful for dev, but host-dependent and less portable). tmpfs mounts stay in memory and disappear on container stop (good for secrets).
Is Docker safe for production?
Yes, with the usual caveats. Run a vulnerability scanner (Trivy, Snyk, Docker Scout) on every image before deploy. Don't run containers as root inside the image — add a USER directive. Pin base image versions (not :latest). Update regularly; old images are the most common entry point in container-related incidents. And treat your Dockerfiles like code: review them, lint them (Hadolint), and rebuild on a schedule.
Wrapping Up
Docker turns "it works on my machine" into "it works on every machine." Start with the four core concepts (image, container, layer, registry), learn the five essential commands, write a Dockerfile for a small project, and graduate to Compose once you have more than one service. The rest is incremental.
When you're hand-editing docker-compose.yml and chasing a YAML syntax error, drop the file into the DevToolbox JSON Formatter — it catches trailing commas, mismatched quotes, and indentation issues that trip up most first-time Compose users.