Docker for Developers: Containers, Images, and Compose
· 10 min read
Core Concepts
| Concept | What It Is | Analogy |
|---|---|---|
| Image | Read-only template with app + dependencies | A class definition |
| Container | Running instance of an image | An object (instance of class) |
| Dockerfile | Instructions to build an image | A recipe |
| Volume | Persistent storage outside container | An external hard drive |
| Network | Communication between containers | A LAN |
| Registry | Storage for images (Docker Hub) | npm/PyPI for containers |
Dockerfile Best Practices
# Good Dockerfile example
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
Key practices:
- Use multi-stage builds to reduce image size (builder stage + production stage)
- Use alpine base images (5MB vs 900MB for full Ubuntu)
- Copy package.json first, then npm install, then copy source — leverages Docker layer caching
- Run as non-root user (USER node)
- Use .dockerignore to exclude node_modules, .git, logs
- Pin specific versions (node:22-alpine not node:latest)
Generate Dockerfiles with our Dockerfile Generator.
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
# View logs
docker logs -f myapp
# Execute command in running container
docker exec -it myapp sh
# Stop and remove
docker stop myapp && docker rm myapp
# Clean up unused resources
docker system prune -a
# List images with sizes
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
Docker Compose
Docker Compose defines multi-container applications in a single YAML file:
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- '3000:3000'
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- db
- redis
volumes:
- ./src:/app/src # Hot reload in development
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- '5432:5432'
redis:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
pgdata:
# Start all services
docker compose up -d
# View logs
docker compose logs -f app
# Rebuild after code changes
docker compose up -d --build
# Stop everything
docker compose down
# Stop and remove volumes (reset data)
docker compose down -v
Generate compose files with our Docker Compose Generator.
Development Tips
| Tip | Why |
|---|---|
| Use volumes for source code | Hot reload without rebuilding |
| Use .env files | Keep secrets out of docker-compose.yml |
| Use health checks | Ensure services are ready before dependents start |
| Use named volumes for data | Persist database data across restarts |
| Use docker compose watch | Auto-rebuild on file changes (Compose v2.22+) |
Frequently Asked Questions
Docker vs virtual machines?
Containers share the host OS kernel and start in seconds. VMs include a full OS and take minutes to boot. Containers use less memory and disk. VMs provide stronger isolation.
When should I use Docker Compose vs Kubernetes?
Docker Compose for local development and simple deployments (single server). Kubernetes for production orchestration across multiple servers with auto-scaling, rolling updates, and self-healing.
How do I reduce Docker image size?
Use alpine base images, multi-stage builds, .dockerignore, and minimize layers. A Node.js app can go from 1GB to under 100MB with these techniques.
How do I debug a container?
Use docker exec -it container sh to get a shell. Use docker logs -f for output. Use docker inspect for configuration details.