🐳 What is Docker?
›Docker is a platform for building, shipping, and running applications in containers. A container is a lightweight, isolated process that packages the application with everything it needs — code, runtime, libraries, config. The key insight: works on my machine = works everywhere. Before Docker, "it works on my machine" was the most common phrase in engineering. Docker killed that problem.
Container vs Virtual Machine
| Container | Virtual Machine | |
|---|---|---|
| Startup time | Milliseconds | Minutes |
| Size | MB | GB |
| Isolation | Process-level (kernel shared) | Full OS isolation |
| Performance | Near-native | 5-15% overhead |
| Use case | Microservices, CI/CD | Full OS isolation needed |
Core Docker Concepts
- Image — read-only template with layers. Built from Dockerfile. Stored in registry.
- Container — running instance of an image. Ephemeral by default (data lost on stop).
- Dockerfile — instructions to build an image. Each instruction = one layer.
- Registry — stores images. Docker Hub (public), ECR/ACR/Harbor (private).
- Layer cache — unchanged layers are reused. Order of instructions matters for build speed.
📄 Dockerfile — Best Practices
›The Dockerfile is the most important Docker artifact. A bad Dockerfile creates large, slow, insecure images. A good Dockerfile creates small, fast, secure images. Interviewers always ask about Dockerfile best practices — this is where junior and senior engineers differ.
The Most Common Mistake — Wrong Layer Order
Docker caches layers. If layer N changes, all layers N+1 onwards are rebuilt. Dependencies change rarely. Code changes every commit. Always copy dependencies before code.
Multi-Stage Builds — Production Standard
Multi-stage builds use multiple FROM instructions. The final image only contains what the last stage copies. Result: a 1.2GB build image becomes a 50MB production image.
.dockerignore — Always Create This
🖥️ Docker Commands — Complete Reference
›🔍 Troubleshooting — Real Production Issues
›Exit Codes — What They Mean
| Exit Code | Meaning | What to do |
|---|---|---|
0 | Clean exit — app stopped itself | Check if this was expected |
1 | Application error / unhandled exception | Check application logs |
137 | OOMKilled — memory limit exceeded (kill -9) | Increase memory limit or fix memory leak |
139 | Segmentation fault — app crashed | Check for null pointer, buffer overflow |
143 | SIGTERM received — graceful shutdown | Normal — container stopped gracefully |
125 | Docker daemon error | Check docker daemon logs |
126 | Command not executable (permission) | Check file permissions in container |
127 | Command not found | Check CMD/ENTRYPOINT, image contents |
Container keeps restarting — systematic approach
🔒 Docker Security — Production Hardening
›Docker security is one of the most asked topics at senior interviews. Most breaches come from: running as root, using untrusted base images, exposed Docker socket, leaked secrets in image layers.
Security Checklist
| Risk | Impact | Fix |
|---|---|---|
| Running as root | Container escape → host compromise | USER 1000:1000 in Dockerfile |
| :latest tag | Unpredictable builds, supply chain risk | Always pin exact version: python:3.11.4-slim |
| Secrets in ENV | Visible in docker inspect, image history | Use Docker secrets or mount at runtime |
| Secrets in image layers | Even if deleted, in previous layer | Multi-stage build, never ADD secrets to image |
| Exposed Docker socket | Full host access = root on host | Never mount /var/run/docker.sock in production |
| Unscanned images | Known CVEs in production | Trivy scan in CI, block CRITICAL/HIGH |
| No resource limits | One container kills the host | Always set --memory and --cpus |
📦 Registry — Push, Pull, Private Registries
›Registry Options
| Registry | Type | Best for |
|---|---|---|
| Docker Hub | Public/private | Open source, personal projects |
| AWS ECR | Private | AWS-based workloads |
| Azure ACR | Private | Azure/AKS workloads |
| GCP Artifact Registry | Private | GCP/GKE workloads |
| Harbor | Self-hosted | On-premise, air-gapped, vulnerability scanning |
| JFrog Artifactory | Self-hosted/cloud | Enterprise, all artifact types |
🔧 Docker Compose — Local Development
›Docker Compose defines multi-container applications. It is NOT for production (use Kubernetes for that) but essential for local development and integration testing. Every senior engineer should be able to write a Compose file from scratch.
⚡ Docker in CI/CD Pipelines
›In production pipelines Docker builds must be: fast (layer caching), secure (no secrets in image), small (multi-stage), and tagged properly (semantic versioning, never :latest in production).
🏗️ Multi-Stage Builds — Production-Grade Images
›The problem with single-stage builds
A standard Dockerfile includes: the full SDK (JDK, Maven, Node.js), all build tools, source code, test files, and the final compiled artifact. Result: a 600MB image shipped to production containing tools that are never used at runtime. Security risk: larger attack surface. Cost: slower pulls, more registry storage.
Multi-stage build — Java application example
# Stage 1: BUILD — contains Maven, source code, all build tools FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . # Download dependencies separately (cached if pom.xml unchanged) RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests # Stage 2: RUNTIME — contains ONLY the JRE and the compiled JAR FROM eclipse-temurin:17-jre-alpine AS runtime WORKDIR /app # Security: create non-root user RUN addgroup -S appgroup && adduser -S appuser -G appgroup # Copy ONLY the artifact from build stage COPY --from=builder /app/target/myapp-1.0.jar app.jar USER appuser EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
Result: build image is 600MB (never pushed). Runtime image is 180MB (what runs in production). The runtime image has no Maven, no source code, no compiler.
Multi-stage for Node.js
FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production # install only production deps FROM node:20-alpine AS runtime WORKDIR /app RUN addgroup -S app && adduser -S app -G app COPY --from=builder /app/node_modules ./node_modules COPY --chown=app:app src ./src COPY package.json . USER app EXPOSE 3000 CMD ["node", "src/index.js"]
Build cache optimisation — order matters
Docker caches each layer. If a layer's input hasn't changed, it uses the cache. Rule: put things that change infrequently (dependencies) BEFORE things that change frequently (source code). Always copy package.json BEFORE copying source code. If you copy source first, the dependency install never uses cache.
⚡ BuildKit — Modern Docker Build Features
›BuildKit is the modern Docker build engine (default in Docker 23+)
# Enable BuildKit (older Docker versions) export DOCKER_BUILDKIT=1 # Build with BuildKit — parallel stages, better caching docker buildx build -t myapp:v1 . # Cache mount — cache pip/npm/maven downloads between builds FROM python:3.11-slim # --mount=type=cache: this directory is cached between builds # never included in the image layer RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt # Secret mount — pass secrets without including them in image layers RUN --mount=type=secret,id=npm_token NPM_TOKEN=$(cat /run/secrets/npm_token) npm install # Build with secret docker buildx build --secret id=npm_token,src=.npmrc -t myapp . # Multi-platform build — build for linux/amd64 and linux/arm64 docker buildx build --platform linux/amd64,linux/arm64 -t myapp:v1 --push .
BuildKit advantages
- Parallel stage execution — independent stages build simultaneously
- Cache mounts — package manager caches persist between builds (much faster)
- Secret mounts — secrets never appear in image layers or history
- Multi-platform — build for ARM and AMD64 in one command
- Inline cache — embed cache metadata in the image for CI/CD layer caching
🔍 Image Scanning and Security in Production
›Every image must be scanned before production
| Tool | How to use | What it finds |
|---|---|---|
| Trivy | trivy image myapp:v1 | OS package CVEs, language dependency CVEs, misconfigurations |
| Snyk | Integrates with GitHub, CI/CD | CVEs + license issues + code secrets |
| Grype | grype myapp:v1 | CVEs in OS and language packages |
| Docker Scout | Built into Docker Desktop | CVEs with remediation advice |
Trivy in CI/CD pipeline — fail on HIGH/CRITICAL
# Jenkins/GitHub Actions step
trivy image --exit-code 1 \ # fail the build if vulnerabilities found
--severity HIGH,CRITICAL \ # only fail on HIGH or CRITICAL
--ignore-unfixed \ # ignore CVEs with no fix available
myapp:${BUILD_NUMBER}
# Output as SARIF for GitHub Security tab
trivy image --format sarif --output trivy-results.sarif myapp:v1
Security best practices — summary
- Never run as root — always add a non-root user in Dockerfile:
USER appuser - Use minimal base images — Alpine, distroless, or slim variants
- No secrets in image layers — use BuildKit secret mounts or inject at runtime via env vars/K8s secrets
- Pin base image versions —
FROM node:20.11.0-alpine3.19notFROM node:latest - Read-only filesystem —
docker run --read-onlyor KubernetesreadOnlyRootFilesystem: true - Scan in registry — ACR, ECR, Harbor all support automatic image scanning on push