Containers are lightweight, isolated environments that package an application with all its dependencies. Unlike virtual machines, containers share the host OS kernel, making them fast to start and efficient with resources. They use Linux kernel features (namespaces for isolation, cgroups for resource limits) to provide process-level isolation.
Core Concepts
Image: A read-only template containing the application, runtime, libraries, and dependencies. Images are built in layers.
Container: A running instance of an image. Ephemeral by default—data is lost when the container stops unless persisted to a volume.
Layer: Each instruction in a Dockerfile creates a layer. Layers are cached and reused across builds. Unchanged layers don’t need rebuilding.
Registry: A repository for storing and distributing container images (e.g., Docker Hub, GitHub Container Registry, AWS ECR).
Dockerfile Fundamentals
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]Key Instructions
| Instruction | Purpose |
|---|---|
FROM | Base image (use specific tags, not latest) |
WORKDIR | Set working directory |
COPY / ADD | Copy files (prefer COPY; ADD can extract archives) |
RUN | Execute commands during build |
ENV | Set environment variables |
ARG | Build-time variables (not available at runtime) |
EXPOSE | Document which ports the container listens on |
USER | Set the user to run subsequent commands |
ENTRYPOINT | Configure container as executable |
CMD | Default arguments for ENTRYPOINT |
Note: CMD cannot access build args (ARG). Use ENV if you need values at runtime.
Best Practices
Multi-stage Builds
Separate build dependencies from the final image. Build tools, compilers, and dev dependencies stay in earlier stages.
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]Minimal Base Images
| Image | Size | Use Case |
|---|---|---|
scratch | 0 MB | Statically compiled binaries (Go, Rust) |
alpine | ~5 MB | When you need a shell/package manager |
distroless | ~2-20 MB | Production workloads, no shell |
*-slim | Varies | Reduced versions of full images |
Layer Caching
Order instructions from least to most frequently changing:
# Dependencies change less often than source code
COPY go.mod go.sum ./
RUN go mod download
# Source code changes frequently
COPY . .
RUN go buildCache Mounts (BuildKit)
For package manager caches, use cache mounts to speed up builds:
# Go - use sharing=locked to prevent concurrent access issues
RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \
--mount=type=cache,target=/root/.cache/go-build,sharing=locked \
go build -o /app/server
# Node.js
RUN --mount=type=cache,target=/root/.npm \
npm ci
# Python
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txtSecurity
- Run as non-root: Use
USER nonrootor create a dedicated user - Use read-only root filesystem:
docker run --read-only - Drop capabilities: Containers run with reduced Linux capabilities by default
- Scan images: Use tools like Trivy or Docker Scout
- Pin base image digests for reproducible builds:
FROM alpine:3.21@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c - Use
.dockerignoreto exclude secrets,.git,node_modules, etc.
General Tips
- One process per container (decoupled, scalable)
- Make containers ephemeral and stateless
- Don’t install unnecessary packages
- Combine
apt-get update && apt-get installin a singleRUNto avoid cache issues - Clean up in the same layer:
rm -rf /var/lib/apt/lists/*
Container Runtimes
Docker: The original and most popular. Includes Docker Engine (daemon), CLI, and Docker Desktop (GUI for macOS/Windows).
containerd: Industry-standard runtime, CNCF graduated project. Used by Docker, Kubernetes, and cloud providers (GKE, EKS, AKS). Focuses on simplicity and portability.
Podman: Daemonless, rootless alternative to Docker. Drop-in replacement (alias docker=podman). Better suited for environments where running a daemon is undesirable.
| Feature | Docker | containerd | Podman |
|---|---|---|---|
| Daemon | Yes | Yes | No |
| Rootless | Optional | Optional | Default |
| Docker CLI compatible | Native | Via nerdctl | Yes |
| Kubernetes runtime | Via containerd | Yes | Via CRI-O |
| Pod support | Docker Compose | No | Native |
Container Registries
| Registry | URL | Notes |
|---|---|---|
| Docker Hub | hub.docker.com | Default public registry |
| GitHub Container Registry | ghcr.io | Integrated with GitHub |
| AWS ECR | .dkr.ecr..amazonaws.com | Private, integrated with AWS |
| Google Artifact Registry | *-docker.pkg.dev | Successor to GCR |
| Azure Container Registry | *.azurecr.io | Private, integrated with Azure |
| Quay.io | quay.io | Red Hat, supports vulnerability scanning |
Commands Cheatsheet
Images
docker build -t myapp:latest . # Build image
docker build --no-cache -t myapp . # Build without cache
docker pull nginx:alpine # Pull from registry
docker push myrepo/myapp:v1 # Push to registry
docker images # List local images
docker rmi myapp:latest # Remove image
docker image prune # Remove dangling images
docker image prune -a # Remove all unused imagesContainers
docker run -d -p 8080:80 nginx # Run detached, map ports
docker run -it alpine sh # Interactive shell
docker run --rm alpine echo "hi" # Remove after exit
docker run -v $(pwd):/app node # Bind mount
docker run -e NODE_ENV=prod myapp # Set env var
docker ps # List running containers
docker ps -a # List all containers
docker stop <container> # Stop gracefully
docker kill <container> # Force stop
docker rm <container> # Remove container
docker logs -f <container> # Follow logs
docker exec -it <container> sh # Shell into running container
docker inspect <container> # Detailed info (JSON)Cleanup
docker system prune # Remove unused data
docker system prune -a --volumes # Remove everything unused
docker volume prune # Remove unused volumes
docker network prune # Remove unused networksDebugging
docker logs <container> --tail 100 # Last 100 lines
docker stats # Live resource usage
docker top <container> # Running processes
docker diff <container> # Changed files
docker cp <container>:/path ./local # Copy files outResources
- Dockerfile reference
- Docker best practices
- Distroless images
- Dive - Explore image layers
- Hadolint - Dockerfile linter