#Dockerfile Best Practices
Master writing efficient, secure, and maintainable Dockerfiles.
#🎯 Learning Objectives
- Write optimized Dockerfiles
- Use multi-stage builds
- Implement security best practices
- Minimize image size
#Basic Dockerfile Structure
dockerfile
1# Base image
2FROM node:20-alpine
3
4# Set working directory
5WORKDIR /app
6
7# Copy dependency files first (better caching)
8COPY package*.json ./
9
10# Install dependencies
11RUN npm ci --only=production
12
13# Copy application code
14COPY . .
15
16# Expose port
17EXPOSE 3000
18
19# Set non-root user
20USER node
21
22# Start command
23CMD ["node", "server.js"]#Essential Instructions
| Instruction | Purpose | Example |
|---|---|---|
FROM | Base image | FROM node:20-alpine |
WORKDIR | Set working directory | WORKDIR /app |
COPY | Copy files | COPY . . |
ADD | Copy + extract | ADD app.tar.gz /app |
RUN | Execute command | RUN npm install |
ENV | Set environment variable | ENV NODE_ENV=production |
EXPOSE | Document port | EXPOSE 3000 |
USER | Set user | USER node |
CMD | Default command | CMD ["npm", "start"] |
ENTRYPOINT | Fixed command | ENTRYPOINT ["python"] |
ARG | Build-time variable | ARG VERSION=1.0 |
LABEL | Metadata | LABEL maintainer="dev@example.com" |
#Multi-Stage Builds
Reduce image size by separating build and runtime:
#Node.js Example
dockerfile
1# Build stage
2FROM node:20-alpine AS builder
3WORKDIR /app
4COPY package*.json ./
5RUN npm ci
6COPY . .
7RUN npm run build
8
9# Production stage
10FROM node:20-alpine AS production
11WORKDIR /app
12COPY /app/dist ./dist
13COPY /app/package*.json ./
14RUN npm ci --only=production
15USER node
16EXPOSE 3000
17CMD ["node", "dist/server.js"]#Go Example
dockerfile
1# Build stage
2FROM golang:1.21-alpine AS builder
3WORKDIR /app
4COPY go.* ./
5RUN go mod download
6COPY . .
7RUN CGO_ENABLED=0 GOOS=linux go build -o /app/main
8
9# Production stage
10FROM alpine:3.18
11RUN apk --no-cache add ca-certificates
12COPY /app/main /main
13USER nobody
14ENTRYPOINT ["/main"]#Python Example
dockerfile
1# Build stage
2FROM python:3.11-slim AS builder
3WORKDIR /app
4RUN pip install --user pipenv
5COPY Pipfile* ./
6RUN pipenv install --deploy --system
7
8# Production stage
9FROM python:3.11-slim
10WORKDIR /app
11COPY /root/.local /root/.local
12COPY . .
13ENV PATH=/root/.local/bin:$PATH
14USER nobody
15CMD ["python", "app.py"]#Layer Optimization
#Order Instructions by Change Frequency
dockerfile
1# ✅ GOOD: Dependencies change less often than code
2FROM node:20-alpine
3WORKDIR /app
4
5# 1. Copy dependency files (rarely change)
6COPY package*.json ./
7
8# 2. Install dependencies (cached if package.json unchanged)
9RUN npm ci
10
11# 3. Copy source code (changes often)
12COPY . .
13
14CMD ["node", "server.js"]#Combine RUN Commands
dockerfile
1# ❌ BAD: Multiple layers
2RUN apt-get update
3RUN apt-get install -y curl
4RUN apt-get install -y wget
5RUN apt-get clean
6
7# ✅ GOOD: Single layer, cleanup included
8RUN apt-get update && \
9 apt-get install -y --no-install-recommends \
10 curl \
11 wget && \
12 apt-get clean && \
13 rm -rf /var/lib/apt/lists/*#Security Best Practices
#Use Non-Root User
dockerfile
1FROM node:20-alpine
2
3# Create app user
4RUN addgroup -g 1001 appgroup && \
5 adduser -u 1001 -G appgroup -D appuser
6
7WORKDIR /app
8COPY . .
9
10USER appuser
11CMD ["node", "server.js"]#Use Specific Tags
dockerfile
1# ❌ BAD: Unpredictable
2FROM node:latest
3FROM python
4
5# ✅ GOOD: Specific versions
6FROM node:20.10.0-alpine3.18
7FROM python:3.11.6-slim-bookworm#Minimal Base Images
| Base | Size | Use Case |
|---|---|---|
alpine | ~5MB | Most applications |
slim | ~50MB+ | When Alpine doesn't work |
distroless | ~20MB | Maximum security |
scratch | 0MB | Static binaries |
dockerfile
1# Smallest possible for Go
2FROM scratch
3COPY /app/main /main
4ENTRYPOINT ["/main"]
5
6# Google distroless
7FROM gcr.io/distroless/static-debian11
8COPY /app/main /main
9ENTRYPOINT ["/main"]#Don't Store Secrets
dockerfile
1# ❌ BAD: Secret in image
2ENV API_KEY=secret123
3
4# ✅ GOOD: Pass at runtime
5# docker run -e API_KEY=secret123 myapp#.dockerignore
Create .dockerignore to exclude files:
1# Dependencies
2node_modules
3vendor
4
5# Build artifacts
6dist
7build
8*.pyc
9__pycache__
10
11# Development
12.git
13.gitignore
14*.md
15Dockerfile*
16docker-compose*
17
18# IDE
19.vscode
20.idea
21
22# OS
23.DS_Store
24Thumbs.db
25
26# Secrets
27.env
28*.pem
29*.key#Image Size Comparison
| Approach | Example Size |
|---|---|
| Full base | 1.2 GB |
| Slim base | 200 MB |
| Alpine base | 50 MB |
| Multi-stage + Alpine | 30 MB |
| Distroless | 20 MB |
| Scratch (Go) | 10 MB |
#Build Commands
bash
1# Basic build
2docker build -t myapp:1.0 .
3
4# Build with different Dockerfile
5docker build -t myapp:1.0 -f Dockerfile.prod .
6
7# Build with arguments
8docker build --build-arg VERSION=1.0 -t myapp:1.0 .
9
10# No cache (fresh build)
11docker build --no-cache -t myapp:1.0 .
12
13# Show build output
14docker build --progress=plain -t myapp:1.0 .[!TIP] Pro Tip: Use
docker build --target builderto build only up to a specific stage for debugging.
[!IMPORTANT] Security: Scan your images with
docker scan myapp:1.0or tools like Trivy before deploying.