Docker has revolutionized how developers build, ship, and run applications. Containerization eliminates "it works on my machine" problems and makes development environments reproducible. But getting started with Docker can be intimidating.
This guide covers Docker from basics to advanced compose files, helping you containerize your applications and streamline your development workflow.
What is Docker?
Docker is a platform for developing, shipping, and running applications in containers. Containers are lightweight, standalone packages that include everything needed to run an application: code, runtime, libraries, and settings.
Key Benefits:
- ✅ Consistent development and production environments
- ✅ Eliminates dependency conflicts
- ✅ Quick application deployment
- ✅ Better resource utilization
- ✅ Simplified CI/CD pipelines
Installing Docker
macOS
# Using Homebrew
brew install --cask docker
# Or download from docker.com
Linux
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# Add your user to docker group
sudo usermod -aG docker $USER
Windows
Download Docker Desktop from docker.com and run the installer.
Docker Basics
Images and Containers
- Image: A read-only template for creating containers
- Container: A running instance of an image
Analogy: Images are like classes, containers are like objects instantiated from those classes.
Key Commands
# Pull an image
docker pull nginx:latest
# Run a container
docker run -d -p 80:80 --name my-nginx nginx
# List running containers
docker ps
# List all containers
docker ps -a
# Stop a container
docker stop my-nginx
# Start a stopped container
docker start my-nginx
# Remove a container
docker rm my-nginx
# Remove an image
docker rmi nginx:latest
Dockerfile: Building Your Own Images
A Dockerfile is a script for building Docker images. It defines the base image, dependencies, and how to run your application.
Node.js Application Dockerfile
# Use official Node.js image
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install --production
# Copy application code
COPY . .
# Expose port
EXPOSE 3000
# Start application
CMD ["node", "server.js"]
Python Application Dockerfile
# Use official Python image
FROM python:3.13-slim
# Set working directory
WORKDIR /app
# Copy requirements
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Expose port
EXPOSE 8000
# Start application
CMD ["python", "app.py"]
Building an Image
docker build -t my-app:1.0 .
Docker Compose: Multi-Container Applications
Docker Compose defines and runs multi-container Docker applications. It's perfect for development with database, cache, and app containers.
docker-compose.yml Example
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
depends_on:
- db
- redis
environment:
- NODE_ENV=development
- DB_HOST=db
- REDIS_HOST=redis
db:
image: postgres:15
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=apppassword
- POSTGRES_DB=appdb
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:
Compose Commands
# Start all services
docker-compose up
# Start in background
docker-compose up -d
# Stop services
docker-compose down
# Rebuild and start
docker-compose up --build
# View logs
docker-compose logs -f app
# Scale services
docker-compose up -d --scale app=3
Development Workflow
1. Use Named Volumes
Named volumes persist data even when containers are removed. Use them for databases and code mounts.
volumes:
- node_modules:/app/node_modules
- postgres_data:/var/lib/postgresql/data
2. Mount Source Code
Mount your source code for hot-reloading during development.
services:
app:
volumes:
- .:/app
- /app/node_modules
3. Use .dockerignore
Exclude unnecessary files from builds to speed up builds and reduce image size.
node_modules
.git
.env
dist
*.log
Production Optimizations
Multi-Stage Builds
Reduce image size by building in one stage and copying only artifacts to the final stage.
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 80
CMD ["node", "dist/server.js"]
Use Alpine Images
Alpine Linux images are smaller and more secure than full images.
FROM node:20-alpine # ~180MB
# Instead of
FROM node:20 # ~900MB
Optimize Layer Caching
Order Dockerfile commands to cache dependencies and only rebuild when they change.
COPY package.json package-lock.json ./
RUN npm install # Cached unless package.json changes
COPY . . # Rebuild only when code changes
Networking
Bridge Networking
Default networking mode where containers communicate via a bridge.
docker network create my-network
docker run --network my-network my-app
Host Networking
Use host networking for performance when networking overhead isn't needed.
docker run --network host my-app
Docker for CI/CD
GitHub Actions with Docker
name: Docker CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t my-app:$GITHUB_SHA .
- name: Run tests
run: docker run my-app:$GITHUB_SHA npm test
- name: Push to registry
run: docker push my-registry/my-app:$GITHUB_SHA
Docker Tips and Tricks
1. Clean Up Unused Resources
# Remove stopped containers
docker container prune
# Remove unused images
docker image prune
# Remove unused volumes (use with caution!)
docker volume prune
# Remove everything
docker system prune
2. View Container Logs
# View logs
docker logs my-container
# Follow logs
docker logs -f my-container
# View last 100 lines
docker logs --tail 100 my-container
3. Execute Commands in Running Container
# Interactive shell
docker exec -it my-container sh
# Run single command
docker exec my-container npm test
4. Copy Files Between Host and Container
# Copy from host to container
docker cp local_file.txt my-container:/app/
# Copy from container to host
docker cp my-container:/app/remote_file.txt ./
5. Check Container Resource Usage
docker stats
Common Pitfalls to Avoid
1. Running Everything as Root
Use non-root users for security.
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
USER appuser
2. Hardcoding Environment Variables
Pass environment variables via docker-compose or -e flag, never hardcode secrets.
3. Building Giant Images
Use multi-stage builds, Alpine images, and .dockerignore to keep images small.
4. Not Cleaning Up
Regularly prune unused images and containers to reclaim disk space.
2026 Docker Updates
Docker 27.0
- 🆕 Improved build performance with BuildKit 2.0
- 🆕 Better networking performance
- 🆕 Enhanced security scanning
- 🆕 New containerd integration
- 🆕 Improved Windows WSL2 support
Learning Path
Week 1
- ☐ Install Docker
- ☐ Run your first container (nginx)
- ☐ Build a simple Dockerfile
- ☐ Learn basic Docker commands
Week 2
- ☐ Create docker-compose.yml for your app
- ☐ Add database and cache containers
- ☐ Learn volume management
- ☐ Practice multi-stage builds
Week 3+
- ☐ Optimize images for production
- ☐ Set up CI/CD with Docker
- ☐ Learn Docker networking
- ☐ Explore advanced features (swarm, Kubernetes)
Conclusion
Docker transforms how developers work by providing consistent, reproducible environments. Start with simple containers using docker-compose, then optimize for production with multi-stage builds and Alpine images.
Don't feel overwhelmed — Docker has a learning curve, but once mastered, it becomes an essential part of your development toolkit.
This article contains affiliate links to Docker registries and tools. If you click through and sign up or purchase, I may earn a commission at no additional cost to you. I use Docker daily and recommend it to all developers.