πŸ“–Siyuan's Notes
δΈ­ζ–‡
Tools2026-05-22
#Docker#Containerization#DevOps#Deployment#Development

Docker for Beginners: Containerize Your Apps from Zero to Production in 2026

Docker is the containerization platform that changed how developers build, ship, and run applications. Instead of worrying about operating system differences, dependency versions, and environment conflicts, you package your application and all its dependencies into a container that runs identically everywhere. This guide takes you from zero to containerizing real applications, writing Dockerfiles, using docker-compose for multi-service apps, and deploying to production.

Why Docker in 2026

The containerization and deployment landscape has several options. Here is how Docker compares.

Tool Type Cost Learning Curve Use Case Community
Docker Container runtime Free (Docker Desktop: $0 for individuals) Medium App containerization Massive
Podman Container runtime Free Medium Docker alternative (daemonless) Growing
Kubernetes Container orchestration Free (self-hosted) Very hard Large-scale container orchestration Massive
Vagrant Virtual machines Free Medium Full OS virtualization Medium
Docker Swarm Container orchestration Free Easy-Medium Small-scale orchestration Declining
Render/Railway PaaS deployment $0-$50+/mo Easy Zero-config deployment Growing

Docker wins on developer experience and ecosystem. It is the industry standard for containerizing applications. Every major cloud provider, CI/CD platform, and deployment tool supports Docker containers. If you learn one containerization tool, make it Docker.

Docker Pricing in 2026

Product Price Best For
Docker Engine (Linux) Free Server-side container runtime
Docker Desktop (individual) Free (personal use) Local development on Mac/Windows
Docker Desktop (business) $9-$21/user/mo Organizations with 250+ employees
Docker Hub (public) Free Public image hosting
Docker Hub (private, free) 1 private repo Personal projects
Docker Hub (Pro) $5/mo Unlimited private repos
Docker Hub (Team) $9/user/mo Team collaboration
Docker Compose Free Multi-container orchestration
Docker Build Cloud $0-$60+/mo Cloud-native build caching

For individual developers and small teams: Docker Desktop is free for personal use and organizations with fewer than 250 employees or less than $10 million in annual revenue.

Step 1: Install Docker Desktop

System Requirements

OS Minimum Requirements
macOS macOS 12+ (Monterey), 4GB RAM (8GB recommended)
Windows Windows 10/11 64-bit, WSL 2 enabled, 4GB RAM
Linux 64-bit kernel 3.10+, 2GB RAM minimum

Installing on macOS

  1. Go to docker.com/products/docker-desktop
  2. Click Download for Mac β€” choose Intel or Apple Silicon
  3. Double-click the downloaded .dmg file
  4. Drag Docker to Applications folder
  5. Open Docker from Applications
  6. Grant necessary permissions
  7. Docker whale icon appears in menu bar β€” Docker is running

Installing on Windows

  1. Download Docker Desktop for Windows
  2. Run the installer
  3. Ensure WSL 2 is selected (not Hyper-V)
  4. Restart your computer
  5. Open Docker Desktop
  6. Verify WSL 2 integration: Settings > Resources > WSL Integration

Installing on Linux (Ubuntu/Debian)

# Update package index
sudo apt-get update

# Install prerequisites
sudo apt-get install -y ca-certificates curl gnupg

# Add Docker's GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Add your user to the docker group
sudo usermod -aG docker $USER

# Apply group change
newgrp docker

Verifying Installation

# Check Docker version
docker --version
# Output: Docker version 26.0.0, build 2ae903e

# Check Docker Compose version
docker compose version
# Output: Docker Compose version v2.26.0

# Run the hello-world container
docker run hello-world
# If you see "Hello from Docker!" β€” Docker is working

# View running containers
docker ps

# View all containers (including stopped)
docker ps -a

# View downloaded images
docker images

Step 2: Understand Docker Core Concepts

Before writing Dockerfiles, understand these core concepts:

Concept What It Is Analogy
Image Read-only template with app + dependencies A recipe
Container Running instance of an image A baked cake
Dockerfile Text file with instructions to build an image The recipe instructions
Volume Persistent storage for containers A hard drive
Network Communication layer between containers A LAN cable
Registry Storage for images (Docker Hub, private) An app store
Docker Compose Tool for defining multi-container apps A restaurant menu

Docker Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Docker Host               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚     Docker Daemon            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”         β”‚  β”‚
β”‚  β”‚  β”‚Ctnr 1β”‚  β”‚Ctnr 2β”‚         β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”˜         β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚  β”‚
β”‚  β”‚  β”‚   Images        β”‚         β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚  β”‚
β”‚  β”‚  β”‚   Volumes       β”‚         β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Docker CLI (client)      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↕
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Docker Hub (Registry)       β”‚
β”‚  - Base images (ubuntu, node)     β”‚
β”‚  - Application images            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 3: Write Your First Dockerfile

Let's containerize a simple Node.js application.

Project Structure

my-app/
β”œβ”€β”€ package.json
β”œβ”€β”€ server.js
└── Dockerfile

The Application (server.js)

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Docker!', timestamp: new Date().toISOString() });
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

package.json

{
  "name": "my-docker-app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

The Dockerfile

# Use the official Node.js runtime as base image
FROM node:20-alpine

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json first (for caching)
COPY package*.json ./

# Install production dependencies only
RUN npm ci --only=production

# Copy the rest of the application code
COPY . .

# Expose the port the app runs on
EXPOSE 3000

# Define the user (security best practice)
USER node

# Start the application
CMD ["node", "server.js"]

Dockerfile Instructions Explained

Instruction What It Does Why It Matters
FROM Sets the base image Starting point β€” includes OS + runtime
WORKDIR Sets working directory All subsequent commands run here
COPY Copies files from host to container Brings your code into the image
RUN Executes a command during build Installs dependencies, runs scripts
EXPOSE Documents which port to expose Informational β€” doesn't actually publish
USER Sets the user for the container Security β€” don't run as root
CMD Default command to run The entry point of your application
ENV Sets environment variables Configure app at build time
ARG Build-time variables Customize the build

Building the Image

# Build the image and tag it
docker build -t my-app:1.0 .

# The . at the end means "use current directory as build context"

Running the Container

# Run the container
docker run -d --name my-app-container -p 3000:3000 my-app:1.0

# -d: Run in detached mode (background)
# --name: Give the container a name
# -p: Map host port 3000 to container port 3000

# Test it
curl http://localhost:3000
# Output: {"message":"Hello from Docker!","timestamp":"2026-05-22T..."}

# View logs
docker logs my-app-container

# Stop the container
docker stop my-app-container

# Remove the container
docker rm my-app-container

Step 4: Dockerfile Best Practices

Multi-Stage Builds

Multi-stage builds create smaller images by separating the build environment from the runtime:

# Stage 1: Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production stage
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Image Size Comparison

Base Image Size Best For
node:20 1.1 GB Full Linux with all tools
node:20-slim 250 MB Minimal Linux, smaller
node:20-alpine 180 MB Smallest, Alpine Linux
Multi-stage (alpine) 80-120 MB Production (recommended)

Dockerfile Layer Caching

Docker caches each layer (instruction). Order matters for build speed:

# BAD: Changes to code invalidate the dependency install layer
COPY . .
RUN npm ci

# GOOD: Only changes to package.json invalidate the install layer
COPY package*.json ./
RUN npm ci
COPY . .

Dockerignore File

Create a .dockerignore file to exclude files from the build context:

node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
Dockerfile
docker-compose.yml
.dockerignore
README.md
*.md
coverage
.nyc_output

Step 5: Docker Compose for Multi-Container Apps

Docker Compose lets you define and run multi-container applications with a single YAML file.

Project Structure

my-project/
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ server.js
β”‚   └── Dockerfile
└── nginx/
    └── nginx.conf

docker-compose.yml

version: '3.9'

services:
  # Node.js application
  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - app-network

  # PostgreSQL database
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - app-network

  # Redis cache
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: unless-stopped
    networks:
      - app-network

  # Nginx reverse proxy
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    restart: unless-stopped
    networks:
      - app-network

# Persistent volumes
volumes:
  db-data:
  redis-data:

# Network
networks:
  app-network:
    driver: bridge

Docker Compose Commands

Command What It Does
docker compose up -d Start all services in background
docker compose up --build Rebuild images and start
docker compose down Stop and remove all containers
docker compose down -v Stop and remove containers + volumes
docker compose ps List running services
docker compose logs View all service logs
docker compose logs -f app Follow logs for specific service
docker compose exec app sh Open shell in app container
docker compose restart app Restart a specific service
docker compose stop Stop all services (don't remove)
docker compose start Start previously stopped services

Running the Full Stack

# Start everything
docker compose up -d

# Check status
docker compose ps

# View logs
docker compose logs -f

# The app is now running at:
# - Direct: http://localhost:3000
# - Via Nginx: http://localhost:80

# Stop everything
docker compose down

# Stop and delete data
docker compose down -v

Step 6: Volumes and Persistent Data

Containers are ephemeral β€” data is lost when they are removed. Volumes solve this.

Types of Storage

Type Syntax Use Case
Named volume my-volume:/data Persistent app data (databases, uploads)
Bind mount ./host-dir:/container-dir Development (live code reload)
tmpfs tmpfs:/data In-memory temporary storage

Volume Commands

# List volumes
docker volume ls

# Create a named volume
docker volume create my-data

# Inspect a volume
docker volume inspect my-data

# Remove a volume
docker volume rm my-data

# Remove all unused volumes
docker volume prune

Development with Bind Mounts

For development, mount your local code into the container for live reloading:

# docker-compose.dev.yml
version: '3.9'
services:
  app:
    build: ./app
    ports:
      - "3000:3000"
    volumes:
      - ./app:/app          # Mount source code
      - /app/node_modules    # Prevent host overwrite of node_modules
    environment:
      - NODE_ENV=development
    command: npm run dev     # Use nodemon for auto-reload
# Run development environment
docker compose -f docker-compose.dev.yml up -d

Step 7: Networking

Docker Network Types

Network Type Description Use Case
bridge Default, isolated network Single host, container-to-container
host Uses host's network stack Maximum performance, no isolation
none No networking Isolated containers
overlay Multi-host networking Docker Swarm / Kubernetes

Networking Commands

# List networks
docker network ls

# Create a network
docker network create my-network

# Connect a container to a network
docker network connect my-network my-container

# Inspect a network
docker network inspect my-network

# Remove a network
docker network rm my-network

Container Communication

In docker-compose, containers on the same network can communicate by service name:

# The "app" service can connect to the "db" service using:
# postgresql://user:pass@db:5432/mydb
# "db" is automatically resolved to the database container's IP

services:
  app:
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
  db:
    image: postgres:16-alpine

Step 8: Environment Management

Environment Variables in Docker

Method Syntax When to Use
Dockerfile ENV ENV NODE_ENV=production Build-time defaults
docker run -e docker run -e API_KEY=xyz my-app Runtime override
docker-compose environment environment: - API_KEY=xyz Compose files
.env file .env file in project root Secrets, local config

Using .env Files with Compose

Create a .env file:

# .env
POSTGRES_USER=myuser
POSTGRES_PASSWORD=secretpassword
POSTGRES_DB=myapp
API_KEY=your-api-key-here
PORT=3000

Reference in docker-compose.yml:

services:
  app:
    environment:
      - API_KEY=${API_KEY}
      - PORT=${PORT}
  db:
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

Never commit .env files to git. Add .env to .gitignore.

Step 9: Docker for Different Application Types

Containerizing a Python Flask App

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

USER nobody

EXPOSE 5000

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

Containerizing a Next.js App

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
USER node
EXPOSE 3000
CMD ["npm", "start"]

Containerizing a Static Site with Nginx

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Step 10: Docker Commands Cheat Sheet

Image Commands

Command What It Does
docker build -t name:tag . Build an image from Dockerfile
docker images List all images
docker rmi image-id Remove an image
docker image prune Remove unused images
docker pull image:tag Download an image from registry
docker push image:tag Upload an image to registry
docker tag image:tag image:new-tag Tag an image

Container Commands

Command What It Does
docker run -d -p 8080:80 image Run a container in background
docker run -it image sh Run container with interactive shell
docker ps List running containers
docker ps -a List all containers
docker stop name Stop a running container
docker start name Start a stopped container
docker restart name Restart a container
docker rm name Remove a stopped container
docker logs -f name Follow container logs
docker exec -it name sh Open shell in running container
docker cp file name:/path Copy file to container
docker stats Show resource usage
docker system prune Remove all unused data

Step 11: Optimization and Production

Image Optimization

Technique Impact How
Use Alpine base images -70-80% size FROM node:20-alpine
Multi-stage builds -40-60% size Separate build and runtime stages
.dockerignore Faster builds Exclude unnecessary files
Layer ordering Faster rebuilds Copy dependencies before code
npm ci instead of npm install Deterministic builds Use with package-lock.json
--only=production -40-60% deps Skip devDependencies
Squash layers -10-20% size docker build --squash

Security Best Practices

Practice Why How
Don't run as root Reduces attack surface USER node or USER nobody
Use specific tags Avoid unexpected changes node:20.11-alpine not node:latest
Scan for vulnerabilities Find known CVEs docker scout cves image-name
Use .dockerignore Prevent secret leaks Exclude .env, .git
Use Docker secrets Manage sensitive data docker secret create
Regular base image updates Get security patches docker pull node:20-alpine
Use read-only filesystem Prevent runtime modifications docker run --read-only

Health Checks

Add health checks to your Dockerfile:

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --only=production
USER node
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

Step 12: Deploying Docker Containers

Deploying to a VPS (DigitalOcean, Linode, etc.)

# SSH into your server
ssh root@your-server-ip

# Install Docker (Linux)
curl -fsSL https://get.docker.com | sh

# Clone your project
git clone https://github.com/yourname/my-app.git
cd my-app

# Create .env file
nano .env
# Add your environment variables

# Build and start
docker compose up -d --build

# Check status
docker compose ps

Deploying with CI/CD (GitHub Actions)

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t my-app:${{ github.sha }} .

      - name: Log in to Docker Hub
        run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u ${{ secrets.DOCKER_HUB_USER }} --password-stdin

      - name: Push image
        run: |
          docker tag my-app:${{ github.sha }} yourname/my-app:${{ github.sha }}
          docker tag my-app:${{ github.sha }} yourname/my-app:latest
          docker push yourname/my-app:${{ github.sha }}
          docker push yourname/my-app:latest

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: root
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /app
            docker compose pull
            docker compose up -d
            docker image prune -f

Deployment Platform Comparison

Platform Cost Docker Support Best For
DigitalOcean Droplet $4-$48/mo Full SSH + Docker Full control, VPS
Railway $0-$20+/mo Dockerfile deploy Simple PaaS
Render $0-$59+/mo Dockerfile deploy Managed PaaS
Fly.io $0-$25+/mo Dockerfile + Fly CLI Edge deployment
AWS ECS/Fargate Pay per use Full Docker support Enterprise scale
Google Cloud Run $0-$20+/mo Container deploy Serverless containers

Docker for Side Hustles and Freelancers

Use Case Without Docker With Docker Savings
Local dev environment setup 2-4 hours per project 10-15 minutes 3+ hours
Onboarding new developer 1-2 days 30 minutes 1-2 days
"Works on my machine" bugs Frequent Eliminated Significant
Deploy to new server 2-4 hours config 10 minutes 2-3 hours
Run multiple projects Dependency conflicts Complete isolation Priceless
Database setup Manual install + config docker compose up 1+ hours

Offering Docker as a Service

If you are a freelance developer, offering Docker containerization as a service:

Service Price Time with Docker Effective Rate
Containerize existing app $500-$1,500 2-4 hours $125-$375/hr
Docker Compose setup $300-$800 1-2 hours $150-$400/hr
CI/CD pipeline with Docker $1,000-$3,000 4-6 hours $167-$500/hr
Production deployment $500-$2,000 2-3 hours $167-$667/hr

Common Pitfalls and Solutions

Pitfall 1: Using latest Tag

# BAD: latest changes unpredictably
FROM node:latest

# GOOD: pin to a specific version
FROM node:20.11-alpine

Pitfall 2: Running as Root

# BAD: runs as root by default
FROM node:20-alpine
CMD ["node", "server.js"]

# GOOD: switch to non-root user
FROM node:20-alpine
USER node
CMD ["node", "server.js"]

Pitfall 3: Large Images

# Check image size
docker images

# Use dive to inspect layers
docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive my-app:1.0

Pitfall 4: Not Using .dockerignore

Without .dockerignore, the entire build context (including node_modules, .git) is sent to the Docker daemon, slowing builds and risking secret leaks.

Pitfall 5: Storing Secrets in Dockerfiles

# BAD: secrets in the image
ENV API_KEY=sk-1234567890

# GOOD: pass at runtime
# docker run -e API_KEY=sk-1234567890 my-app
# Or use Docker secrets

Action Checklist

  • Install Docker Desktop (Mac/Windows) or Docker Engine (Linux)
  • Verify installation: docker run hello-world
  • Learn 10 essential docker commands (build, run, ps, logs, exec, stop, rm, images, volume, compose)
  • Write a Dockerfile for a simple Node.js app
  • Build the image: docker build -t my-app:1.0 .
  • Run the container: docker run -d -p 3000:3000 my-app:1.0
  • Test the running app with curl
  • View container logs: docker logs
  • Open a shell in the container: docker exec -it container-name sh
  • Create a .dockerignore file
  • Convert to a multi-stage build
  • Compare image sizes (before and after optimization)
  • Write a docker-compose.yml with app + database + redis
  • Run the full stack: docker compose up -d
  • Set up volumes for persistent database storage
  • Create a .env file for environment variables
  • Set up a development compose file with bind mounts
  • Add a health check to your Dockerfile
  • Run docker scout cves to scan for vulnerabilities
  • Add a non-root USER instruction to your Dockerfile
  • Deploy the containerized app to a VPS
  • Set up a GitHub Actions CI/CD pipeline with Docker
  • Push images to Docker Hub or a private registry
  • Document your Docker setup in a README

Realistic Productivity Gains

Metric Without Docker With Docker Improvement
Dev environment setup 2-4 hours 10-15 min -90%
Onboarding time 1-2 days 30 min -90%
Deployment consistency 60-80% 99%+ Significant
"Works on my machine" bugs Frequent Eliminated -100%
Multi-project management Conflict-prone Isolated Significant
Production rollback time 30-60 min 2-5 min -90%

Final Word

Docker is the deployment standard for modern applications. It eliminates the "works on my machine" problem, makes onboarding trivial, and ensures your application runs identically in development and production. The learning curve is real but worth it: within a day, you can containerize a Node.js app, set up a multi-service compose file with a database and cache, and deploy to a VPS. Start with the simple Node.js Dockerfile, graduate to docker-compose for multi-service apps, and add multi-stage builds and security practices as you go. For freelance developers, Docker skills are worth $150-$500/hour when offered as a service β€” containerizing existing apps and setting up CI/CD pipelines is a high-value skill that clients actively need. The investment of 2-3 days to learn Docker pays for itself within the first client project.

More guides: bsynet.cc

Tags

#Docker#Containerization#DevOps#Deployment#Development

πŸ“– Related Posts