Docker Best Practices
Contents
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-worldWindows
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-worldLinux (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-worldDockerfile 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
*.logImage 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 sizeCombine Instructions
# Inefficient
RUN pip install flask
RUN pip install requests
RUN pip install redis
# Efficient
RUN pip install flask requests redisOrder 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
EOFNetworking
Expose Ports
# Expose single port
EXPOSE 8000
# Expose multiple ports
EXPOSE 8080 8443Run Container
# Map port
docker run -p 8080:8000 myapp
# Map all exposed ports
docker run -P myappData 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 myappBind Mounts
# Bind local directory
docker run -v $(pwd):/app myapp
# Read-only mount
docker run -v $(pwd):/app:ro myapptmpfs Mount (Memory)
# Store data in memory
docker run --tmpfs /app/tmp myappEnvironment Variables
Set Environment Variables
# Set at build time
ENV NODE_ENV=production
ENV PORT=8080Runtime Override
ENV APP_VERSION=1.0.0
# Can be overridden with -e at runtimeUse 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:1000Scan for Vulnerabilities
# Using Trivy
brew install trivy
trivy image myapp:latest
# Using Docker Scout
docker scout cves myapp:latestHealth Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1Secure Base Images
# Use official verified images
FROM python:3.12-slim-bookworm
# Or use distroless
FROM gcr.io/distroless/nodejs20-debian12Docker 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/bashView Logs
# View logs
docker logs mycontainer
# Follow in real-time
docker logs -f mycontainer
# Recent logs
docker logs --tail 100 mycontainerInspect Container
# View running status
docker ps
# View all containers (including stopped)
docker ps -a
# Inspect container details
docker inspect mycontainer
# View resource usage
docker statsCI/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 }}