Docker for Developers — A Complete Guide
Learn Docker from the ground up: images, containers, Dockerfiles, networks, volumes, and Docker Compose for local development.
Introduction
Docker is a platform for developing, shipping, and running applications in containers. Containers are lightweight, portable, and consistent across environments, solving the “it works on my machine” problem.
Key Concepts
Images
A Docker image is a read-only template containing the application code, runtime, libraries, and dependencies. Images are built from a Dockerfile.
Containers
A container is a runnable instance of an image. It is isolated from the host system and other containers, but can share the OS kernel.
Dockerfile
A text file with instructions to build an image. Each instruction creates a layer in the image.
Dockerfile — What Works
# Use a specific version, not 'latest'
FROM node:20-alpine
# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copy dependency files first for layer caching
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Change ownership
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
What Works
- Use specific image tags —
node:20-alpineinstead ofnode:latest - Run as non-root user — security best practice
- Order instructions by change frequency — put
COPY package.jsonbeforeCOPY .to cache dependencies - Combine RUN commands where possible to reduce layers
- Use
.dockerignoreto avoid sending unnecessary files to the build context
Essential Commands
# Build an image
docker build -t myapp:1.0 .
# Run a container
docker run -d -p 3000:3000 --name myapp myapp:1.0
# List running containers
docker ps
# Stop and remove a container
docker stop myapp && docker rm myapp
# Execute a command inside a running container
docker exec -it myapp sh
# View logs
docker logs -f myapp
# Remove unused images and volumes
docker system prune -a --volumes
Networking
Docker provides several network drivers:
| Driver | Use Case |
|---|---|
| bridge | Default. Isolated network for containers on a single host |
| host | Shares the host’s network stack (no isolation) |
| none | Disables all networking |
| overlay | Connects containers across multiple Docker hosts (Swarm) |
# Create a custom bridge network
docker network create my-network
# Run containers on the same network
docker run -d --name db --network my-network postgres:15
docker run -d --name api --network my-network myapp:1.0
Volumes
Volumes persist data outside the container filesystem:
# Named volume
docker volume create my-data
docker run -v my-data:/data myapp:1.0
# Bind mount (development)
docker run -v $(pwd):/app -v /app/node_modules myapp:1.0
| Type | Use Case |
|---|---|
| Named volume | Persistent data (databases, uploads) |
| Bind mount | Live code reloading during development |
| tmpfs | Ephemeral, in-memory data |
Docker Compose
docker-compose.yml defines and runs multi-container applications:
version: '3.8'
services:
api:
build: ./api
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DB_HOST=db
depends_on:
- db
volumes:
- ./api:/app
- /app/node_modules
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f api
# Rebuild after Dockerfile changes
docker-compose up -d --build
# Stop and remove everything
docker-compose down -v
Multi-Stage Builds
Reduce final image size by separating build and runtime stages:
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]
Common Mistakes
- Running containers as root unnecessarily
- Storing secrets in environment variables or image layers. See container security.
- Not handling signals properly (PID 1 problem) — use
tiniordumb-init - Building production images with devDependencies included. See immutable infrastructure.
- Ignoring
.dockerignore, bloating the build context - Hardcoding configuration in images instead of using env vars
Troubleshooting
- Pipeline fails silently: enable verbose logging and store pipeline artifacts between stages so you can inspect the exact state that failed.
- Container crashes on startup: check that environment variables, secrets, and config files are mounted correctly. Read the first 50 lines of logs before scaling replicas.
- Deployment rolls back repeatedly: verify health checks, resource limits, and startup probes. A failing readiness probe is a common cause of rolling restarts.
- Slow CI builds: cache dependencies and docker layers. Split large test suites into parallel jobs to reduce wall-clock time.
- Drift between environments: use infrastructure-as-code and immutable artifacts.
Advanced Topics
Scenario: Optimized Dockerfile for Node.js
# Multi-stage build: reduce final image from 900MB to 80MB
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Install dev deps only for build
RUN npm ci
COPY . .
RUN npm run build
# Final stage: only production files
FROM node:20-slim AS runner
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app/dist ./dist
# Non-root user for security
RUN groupadd -r app && useradd -r -g app appuser
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/main.js"]
# docker-compose.yml for development
```yaml
version: "3.9"
services:
app:
build: .
ports: ["3000:3000"]
environment:
DATABASE_URL: postgresql://user:pass@db:5432/app
REDIS_URL: redis://cache:6379
depends_on: [db, cache]
volumes:
- ./src:/app/src # Hot reload in development
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports: ["5432:5432"]
volumes: [pgdata:/var/lib/postgresql/data]
cache:
image: redis:7-alpine
ports: ["6379:6379"]
volumes:
pgdata:
Optimizations:
| Technique | Before | After |
|---|---|---|
| Multi-stage | 900MB | 80MB |
| Alpine/slim | 900MB | 180MB |
| npm ci | 60s | 20s |
| Layer cache (copy package first) | Reinstalls all | Cache hit |
| Non-root user | Root | appuser |
| Healthcheck | No check | Auto-restart |
Lessons:
- Multi-stage build reduces size dramatically
- Copy package.json before code uses layer cache
- Non-root user is mandatory in production
- Healthcheck enables auto-restart in orchestrators
- docker-compose for dev, Dockerfile for prod
### How do I debug a container in production?
Use `docker exec -it <container> sh` to enter the container. If it has no shell (distroless), use `docker logs <container>` and `docker inspect`. For network debugging, use `docker run --rm --network container:<id> nicolaka/netshoot`. To see processes: `docker top <container>`. To see resource usage: `docker stats`. Frequently Asked Questions
What is the difference between a VM and a container?
VMs virtualize hardware and include a full OS. Containers virtualize the OS kernel and share it with the host, making them much lighter and faster to start.
How do I debug a failing container?
Use docker logs <container> for stdout/stderr, docker exec -it <container> sh to inspect the filesystem, and docker inspect <container> for detailed configuration.
Should I use Docker Swarm or Kubernetes?
For most new projects, use Kubernetes (or a managed service like EKS, GKE, AKS). See orchestration. Docker Swarm is simpler but has limited ecosystem support and is no longer actively developed by Docker Inc.
Related Resources
CI/CD Pipeline Guide
A practical guide to building CI/CD pipelines with GitHub Actions, testing, deployment strategies, and rollback procedures.
GuideKubernetes Basics for Application Developers
Learn the core Kubernetes concepts every developer needs: Pods, Services, Deployments, ConfigMaps, and basic kubectl commands.
RecipeGenerate Sitemaps Live
How to build and serve live XML sitemaps from your application data, with multi-language support, pagination, and automatic lastmod dates.
RecipeContainer Image Security Scanning with Trivy
Scan Docker images for vulnerabilities, misconfigurations, and secrets using Trivy, integrate scanning into CI/CD pipelines, and enforce image policies before deployment to production
RecipeImplement Graceful Shutdown and Zero-Downtime Restarts
How to implement graceful shutdown and zero-downtime restarts for web servers, workers, and containers
RecipeImmutable Infrastructure
Build immutable infrastructure with versioned machine images and containers to eliminate configuration drift and ensure reproducible deployments.