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

InstructionPurpose
FROMBase image (use specific tags, not latest)
WORKDIRSet working directory
COPY / ADDCopy files (prefer COPY; ADD can extract archives)
RUNExecute commands during build
ENVSet environment variables
ARGBuild-time variables (not available at runtime)
EXPOSEDocument which ports the container listens on
USERSet the user to run subsequent commands
ENTRYPOINTConfigure container as executable
CMDDefault 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

ImageSizeUse Case
scratch0 MBStatically compiled binaries (Go, Rust)
alpine~5 MBWhen you need a shell/package manager
distroless~2-20 MBProduction workloads, no shell
*-slimVariesReduced 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 build

Cache 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.txt

Security

  • Run as non-root: Use USER nonroot or 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 .dockerignore to 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 install in a single RUN to 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.

FeatureDockercontainerdPodman
DaemonYesYesNo
RootlessOptionalOptionalDefault
Docker CLI compatibleNativeVia nerdctlYes
Kubernetes runtimeVia containerdYesVia CRI-O
Pod supportDocker ComposeNoNative

Container Registries

RegistryURLNotes
Docker Hubhub.docker.comDefault public registry
GitHub Container Registryghcr.ioIntegrated with GitHub
AWS ECR.dkr.ecr..amazonaws.comPrivate, integrated with AWS
Google Artifact Registry*-docker.pkg.devSuccessor to GCR
Azure Container Registry*.azurecr.ioPrivate, integrated with Azure
Quay.ioquay.ioRed 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 images

Containers

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 networks

Debugging

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 out

Resources