Docker Best Practices

Sources:

Overview

Key practices for writing efficient, secure, and maintainable Dockerfiles.

Build Optimization

Enable BuildKit

export DOCKER_BUILDKIT=1

BuildKit enables parallel builds in multistage Dockerfiles.

Multistage Builds

# 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 AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Layer Caching

Order commands from least to most frequently changed:

# 1. Base image (rarely changes)
FROM node:20-alpine
 
# 2. System deps (changes occasionally)
RUN apk add --no-cache git
 
# 3. App deps (changes with package.json)
COPY package*.json ./
RUN npm ci
 
# 4. Source code (changes frequently)
COPY . .

Package Management

apt-get Best Practices

RUN apt-get update && apt-get install -y \
    curl \
    git \
    vim \
  && rm -rf /var/lib/apt/lists/*

Rules:

  • Combine update and install in one RUN
  • Alphabetize packages for readability
  • Clean up apt cache to reduce image size
  • Don’t run apt-get upgrade (base image’s job)

Entrypoint Patterns

Script Entrypoint

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]

docker-entrypoint.sh:

#!/bin/bash
set -e
 
if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"
    
    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi
    
    exec gosu postgres "$@"
fi
 
exec "$@"

ENTRYPOINT vs CMD

ENTRYPOINTCMD
Main executableDefault arguments
Rarely overriddenEasily overridden
Use exec form ["..."]Use exec form ["..."]
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
  • docker run s3cmd → shows help
  • docker run s3cmd ls s3://bucket → runs command

Environment Variables

# Update PATH
ENV PATH="/usr/local/nginx/bin:$PATH"
 
# App configuration
ENV NODE_ENV=production \
    PORT=3000

Security

Non-Root User

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

Minimal Base Images

ImageSizeUse Case
alpine~5MBMinimal Linux
distroless~2MBBarebones runtime
scratch0MBStatic binaries only

.dockerignore

node_modules
.git
.env
*.md
Dockerfile
docker-compose*.yml

Build from GitHub

docker build -t myimage:latest -f- https://github.com/owner/repo.git <<EOF
FROM busybox
COPY hello.c .
EOF

See Also


(c) No Clocks, LLC | 2024