Quick Start
Build your first Docker image in 5 minutes:
Python Web App
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
Build & Run
docker build -t my-app
.
docker run -p 8000:8000 my-app
Pro Tip
Create a .dockerignore to exclude node_modules,
.git, *.log.
Auto-generate one →
Fundamentals
A Dockerfile is a blueprint for building container images. It's a text file with commands that Docker executes to assemble your app environment.
Think of it as a recipe: install dependencies, copy files, configure settings, define startup—all automated and reproducible.
Key Terms
- Image
- Read-only template built from Dockerfile
- Container
- Running instance of an image
- Layer
- Each instruction creates a cached layer
- Build Context
- Files available during build (minimize it!)
Syntax
Simple, declarative syntax:
# Comments start with hash
FROM ubuntu:22.04
LABEL maintainer="[email protected]"
RUN apt-get update && apt-get install -y python3
COPY . /app
WORKDIR /app
CMD ["python3", "app.py"]
- Instructions are UPPERCASE (convention, not required)
- Each instruction = new layer
- Order matters—place frequently changing commands last
- Use
\for multi-line commands
Core Instructions
| Command | Purpose | Example |
|---|---|---|
| FROM | Base image (required first) | FROM node:18-alpine |
| RUN | Execute commands | RUN npm install |
| COPY | Copy files from host | COPY . /app |
| WORKDIR | Set working directory | WORKDIR /app |
| EXPOSE | Document ports | EXPOSE 3000 |
| CMD | Default startup command | CMD ["npm", "start"] |
Advanced Instructions
ENV - Environment Variables
Set variables available at runtime:
ENV NODE_ENV=production \
PORT=3000
ARG - Build Arguments
Variables only available during build:
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine
# docker build --build-arg NODE_VERSION=20 .
ENTRYPOINT vs CMD
ENTRYPOINT sets main command; CMD provides default args:
ENTRYPOINT ["python"]
CMD ["app.py"]
# Runs: python app.py
# Override: docker run myapp script.py → python script.py
HEALTHCHECK
Monitor container health:
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
USER - Security Essential
Never run as root in production:
RUN addgroup -g 1001 appuser && \
adduser -S appuser -u 1001
USER appuser
Real-World Examples
Node.js
ProductionFROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN addgroup -g 1001 nodejs && adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
Python FastAPI
ProductionFROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
Go (Multi-Stage)
Optimized# Build
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o main .
# Production
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/main .
RUN adduser -D -u 1000 appuser && chown appuser:appuser main
USER appuser
EXPOSE 8080
CMD ["./main"]
Laravel
ProductionFROM php:8.2-fpm-alpine
RUN apk add --no-cache libpng-dev libzip-dev zip
RUN docker-php-ext-install pdo pdo_mysql zip gd
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
COPY composer.* ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
COPY . .
RUN chown -R www-data:www-data /var/www
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
Best Practices
Use Minimal Base Images
Alpine images are 5-10x smaller:
✓ ~170MB
FROM node:18-alpine
✗ ~700MB+
FROM ubuntu:latest
Chain Commands & Clean Up
Combine operations, remove temp files in one layer:
RUN apt-get update && apt-get install -y \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*
Use .dockerignore
Exclude unnecessary files:
node_modules
.git
*.log
.env
coverage
Optimize Layer Caching
Copy dependencies before source code:
# Dependencies (rarely change) → cached
COPY package*.json ./
RUN npm ci
# Source (changes often) → rebuilt
COPY . .
Pin Specific Versions
Ensure reproducible builds:
✓ Specific
FROM node:18.17.0-alpine3.18
✗ Unpredictable
FROM node:latest
Multi-Stage Builds
Why? Build tools add 100s of MBs. Compile in one stage, copy only artifacts to a minimal runtime image.
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production && \
addgroup -g 1001 nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]
Single Stage
- 📦 450MB+
- 🔨 Build tools
- 📚 All deps
- 🐌 Slow
Multi-Stage
- 📦 120MB
- ✨ Production only
- 🎯 Runtime deps
- 🚀 Fast
Security
Never Run as Root
Create and switch to non-root user:
RUN addgroup -g 1001 appuser && \
adduser -S appuser -u 1001
USER appuser
Never Hardcode Secrets
Use environment variables at runtime:
✗ Never
ENV API_KEY="secret123"
✓ Runtime
docker run -e API_KEY=$API_KEY app
Scan for Vulnerabilities
Regularly scan images:
# Docker Scout
docker scout cves my-image:latest
# Trivy
trivy image my-image:latest
Minimize Attack Surface
- Use minimal base images (Alpine, distroless)
- Remove unnecessary packages
- No debug tools in production
- Use multi-stage builds
Optimization
Layer Caching
Order by change frequency:
- Base image (rarely)
- System packages
- App dependencies
- Source code (often)
Enable BuildKit
Better caching & parallelization:
DOCKER_BUILDKIT=1 docker build -t app .
Prefer COPY Over ADD
COPY is transparent; ADD has hidden features (auto-extraction).
Clean in Same Layer
Remove temp files immediately:
RUN wget file.tar.gz && \
tar -xzf file.tar.gz && \
rm file.tar.gz
Common Mistakes
❌ Using :latest Tag
:latest is unpredictable—breaks reproducibility
Wrong
FROM node:latest
Right
FROM node:18.17.0-alpine
❌ Separating apt-get update
Causes cache issues with outdated packages
Wrong
RUN apt-get update
RUN apt-get install -y pkg
Right
RUN apt-get update && apt-get install -y pkg
❌ Installing Dev Dependencies
Bloats production images unnecessarily
Wrong
RUN npm install
Right
RUN npm ci --only=production
❌ No .dockerignore File
Slows builds, increases image size
Wrong
Missing .dockerignore
Right
Create .dockerignore
Troubleshooting
! Build is Very Slow
Causes
- Large build context
- Poor layer caching
- Network issues
Solutions
Create .dockerignore • Copy deps before source • Enable BuildKit • Use --no-cache
! Container Exits Immediately
Causes
- No foreground process
- App crashes
- Wrong CMD format
Solutions
Check logs: docker logs <id> • Run interactively: docker run -it image sh • Ensure CMD runs in foreground
! Permission Denied
Causes
- Non-root user lacks ownership
- Incorrect permissions
Solutions
Use COPY --chown=user:group • Run chown after copying • Set chmod on directories
! Image Too Large
Causes
- Full OS image
- Build dependencies
- Package caches
Solutions
Use Alpine/distroless • Multi-stage builds • Clean caches • Analyze: docker history