Overview
Docker is the most popular toolkit for building, sharing, and running containers. A Dockerfile describes how to build an image layer by layer; docker build creates the image, docker push uploads it to a registry, and docker run starts containers. Docker Compose defines multi-container applications (app, database, cache) for local development.
Good Docker practice focuses on small, secure, reproducible images: multi-stage builds, minimal base images, layer caching (copy dependency files before source code), non-root users, pinned versions, and vulnerability scanning in CI.
The Dockerfile is a recipe. Building it produces a sealed meal kit (image) with every ingredient measured. Anyone can cook it (run) and get the same meal, in any kitchen.
When to use it
- Packaging applications for any environment.
- Local development environments with dependencies.
- CI pipelines that build and test in containers.
- Shipping images to Kubernetes, ECS, or Cloud Run.
Where it shows up in interviews
Recognize it when: how is the app packaged?
- Design a CI/CD pipeline
- Containerize a monolith
Recognize it when: developers need the same stack locally.
- Set up a local environment with Postgres and Redis
Where it is used in real software
Public images on Docker Hub; companies mirror them in private registries like Artifactory, ECR, or GHCR.
Docker's modern builder with parallel stages, cache mounts, and secrets during builds.
Tools like Trivy, Grype, and Snyk scan images for known vulnerabilities in CI.
Key terms
- Dockerfile
- Instructions to build an image.
- Layer
- Cached filesystem change from one instruction.
- Multi-stage build
- Build in one stage, copy only artifacts into a small runtime stage.
- Docker Compose
- YAML to run multi-container apps.
- Tag / digest
- Human label / immutable content hash.
How it works, step by step
- 1Choose a minimal base image
Alpine, distroless, or slim variants from a trusted registry.
- 2Order layers for caching
Copy package manifests and install dependencies before copying source.
- 3Use multi-stage builds
Keep compilers and dev dependencies out of the final image.
- 4Harden
Non-root user, no secrets in layers, .dockerignore.
- 5Tag, scan, push
Tag with commit SHA; scan in CI; push to the registry.
Effect of image best practices
Node API image
| Approach | Image size | Rebuild after code change | Risk |
|---|---|---|---|
| node:22 full, single stage | ~1.1 GB | Reinstalls all deps | Many CVEs, dev tools shipped |
| Alpine, deps before source | ~180 MB | Reuses dependency layer | Fewer packages |
| Multi-stage, prod deps only | ~120 MB | Seconds | Minimal attack surface |
| Distroless runtime | ~90 MB | Seconds | No shell: hardest to exploit |
NOWApproach: node:22 full, single stage | Image size: ~1.1 GB | Rebuild after code change: Reinstalls all deps | Risk: Many CVEs, dev tools shipped
Smaller images pull faster, start faster, and have fewer vulnerabilities.
Implementation
services: api: build: . ports: ["3000:3000"] environment: DATABASE_URL: postgres://app:app@db:5432/app REDIS_URL: redis://cache:6379 depends_on: db: { condition: service_healthy } db: image: containers.artifactory.tools.bestbuy.com/postgres:16 environment: { POSTGRES_USER: app, POSTGRES_PASSWORD: app, POSTGRES_DB: app } healthcheck: { test: ["CMD", "pg_isready", "-U", "app"], interval: 5s, retries: 10 } volumes: ["pgdata:/var/lib/postgresql/data"] cache: image: containers.artifactory.tools.bestbuy.com/redis:7-alpinevolumes: pgdata: {}Complexity and performance
When only source changes.
Keep images small.
Trade-offs
Distroless images are secure but lack shells; use ephemeral debug containers when needed.
Alpine is small but uses musl, which occasionally causes compatibility issues.
Variants and related techniques
Build images without Dockerfiles (Cloud Native Buildpacks).
Daemonless, rootless Docker-compatible tool.
Common mistakes
- Secrets in Dockerfiles or layers.
Fix: Use build secrets and runtime secret managers.
- COPY . . before installing dependencies.
Fix: Invalidates the dependency cache on every code change.
- No .dockerignore.
Fix: node_modules, .git, and env files bloat or leak into images.
Interview questions
How do you make Docker images small and secure?
Multi-stage builds, minimal or distroless base images, only production dependencies, non-root users, no secrets in layers, pinned versions, and vulnerability scanning in CI.
What is layer caching and how do you use it?
Each instruction creates a cached layer reused when its inputs are unchanged. Copy dependency manifests and install before copying source so code changes do not reinstall dependencies.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Write a multi-stage Dockerfile | Easy | Size and caching. |
| Compose an app with DB, cache, and worker | Medium | Local parity. |