Contents

Docker Best Practices

Quick Start

Installation & Version

Current version is Docker 29.4.

macOS

Docker Desktop is the recommended approach for macOS:

# Install via Homebrew
brew install --cask docker

# Or download Docker Desktop for Mac
# Visit https://docs.docker.com/desktop/install/mac-install/

# Launch Docker Desktop
open -a Docker

# Verify installation
docker --version

# Run hello world
docker run hello-world

Windows

Download and install Docker Desktop for Windows:

# Run the installer Docker Desktop Installer.exe
# Launch Docker Desktop
docker --version

# Run hello world
docker run hello-world

Linux (Ubuntu)

# Update apt package index
sudo apt update

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

# Add Docker 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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verify installation
sudo docker --version

# Run hello world (without sudo after adding user to docker group)
sudo usermod -aG docker $USER
docker run hello-world

Dockerfile Basics

Basic Structure

# Base image
FROM python:3.12-slim

# Maintainer
LABEL maintainer="[email protected]"

# Working directory
WORKDIR /app

# Copy files
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Expose port
EXPOSE 8000

# Start command
CMD ["python", "app.py"]

Common Instructions

Instruction Description
FROM Specify base image
RUN Execute command
COPY Copy files
ADD Copy and extract (supports remote URLs)
WORKDIR Set working directory
EXPOSE Declare ports
ENV Environment variables
CMD Container start command
ENTRYPOINT Entry point

Multi-stage Builds

Reduce image size by keeping only runtime artifacts:

Node.js Example

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

# Run stage
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]

Go Example

# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

# Run stage
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

.dockerignore

Exclude unnecessary files:

# Version control
.git
.gitignore

# Dependencies
node_modules
__pycache__
*.pyc

# Documentation
README.md
docs

# IDE
.vscode
.idea

# Tests
*.test
coverage

# Environment files
.env
.env.*

# Build artifacts
dist
build
*.log

Image Optimization

Use Specific Version Tags

# Avoid latest (can cause unpredictable behavior)
FROM node:20-alpine      # Specify version
FROM python:3.12-slim    # Use slim variant to reduce size

Combine Instructions

# Inefficient
RUN pip install flask
RUN pip install requests
RUN pip install redis

# Efficient
RUN pip install flask requests redis

Order Optimization

# Put rarely changing layers first
COPY package*.json ./
RUN npm ci

# Put frequently changing last
COPY . .

Use Heredoc

# More readable RUN instructions
RUN <<EOF
apt-get update
apt-get install -y curl
apt-get clean
EOF

Networking

Expose Ports

# Expose single port
EXPOSE 8000

# Expose multiple ports
EXPOSE 8080 8443

Run Container

# Map port
docker run -p 8080:8000 myapp

# Map all exposed ports
docker run -P myapp

Data Management

Volumes

# Create volume
docker volume create mydata

# Run with volume
docker run -v mydata:/app/data myapp

# Or use anonymous volume
docker run -v /app/data myapp

Bind Mounts

# Bind local directory
docker run -v $(pwd):/app myapp

# Read-only mount
docker run -v $(pwd):/app:ro myapp

tmpfs Mount (Memory)

# Store data in memory
docker run --tmpfs /app/tmp myapp

Environment Variables

Set Environment Variables

# Set at build time
ENV NODE_ENV=production
ENV PORT=8080

Runtime Override

ENV APP_VERSION=1.0.0
# Can be overridden with -e at runtime

Use ARG and ENV

# ARG only available at build time
ARG APP_ENV=production

# ENV available at build and runtime
ENV APP_ENV=${APP_ENV}

Security Best Practices

Avoid Running as Root

# Create non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

USER appuser

# Or use numeric UID
USER 1000:1000

Scan for Vulnerabilities

# Using Trivy
brew install trivy
trivy image myapp:latest

# Using Docker Scout
docker scout cves myapp:latest

Health Check

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

Secure Base Images

# Use official verified images
FROM python:3.12-slim-bookworm

# Or use distroless
FROM gcr.io/distroless/nodejs20-debian12

Docker Compose

Basic Configuration

# docker-compose.yml


services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - NODE_ENV=production
    volumes:
      - ./data:/app/data
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

volumes:
  db_data:

Debugging

Enter Container

# Interactive shell
docker run -it myapp /bin/sh

# Execute in running container
docker exec -it mycontainer /bin/bash

View Logs

# View logs
docker logs mycontainer

# Follow in real-time
docker logs -f mycontainer

# Recent logs
docker logs --tail 100 mycontainer

Inspect Container

# View running status
docker ps

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

# Inspect container details
docker inspect mycontainer

# View resource usage
docker stats

CI/CD Integration

GitHub Actions

# .github/workflows/docker.yml
name: Docker

on:
  push:
    branches: [main]
    tags: ['v*']

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

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: user/myapp:latest,user/myapp:${{ github.sha }}