Skip to content

Docker packages an application and everything it needs into an image, and runs it as an isolated container. High level concepts are in Containers; this note is the working detail. Running many containers across machines leads to Kubernetes (2. Kubernetes Architecture – from Container Images to Running Pods).

Tools at a glance

Tool Purpose
Docker Engine (dockerd) Daemon that builds and runs containers
Docker CLI (docker) Client talking to the daemon
Docker Desktop GUI + Engine for Windows / macOS / Linux
Docker Hub Public registry. Others: GHCR, AWS ECR, Azure ACR, Quay
Docker Compose Multi-container apps, see 2. Docker Compose
BuildKit / buildx Modern builder, multi-platform images
Docker Swarm Built-in (basic) orchestration
Podman / containerd Alternative runtimes, same OCI images

How It Works

  • Containers are processes isolated with Linux namespaces (pid, net, mnt, uts, ipc, user) and limited with cgroups (CPU, memory). They share the host kernel, unlike VMs
  • Image: read-only, layered filesystem + metadata. Each Dockerfile instruction adds a layer; layers are cached and shared
  • Container: a running instance of an image plus a thin writable layer. Deleting the container deletes that layer
  • Registry: stores images by registry/namespace/name:tag
  • Standards: OCI (Open Container Initiative) image and runtime specs; see Chapter 2 of the Kubernetes notes
Dockerfile ──build──► Image ──push──► Registry ──pull──► Image ──run──► Container

Everyday Commands

$ docker run -d --name web -p 8080:80 nginx:1.27     # run detached, publish port
$ docker ps                                           # running containers (-a for all)
$ docker logs -f web                                  # follow logs
$ docker exec -it web sh                              # shell inside
$ docker stop web && docker rm web                    # stop and remove
$ docker images                                       # local images
$ docker pull python:3.12-slim
$ docker rmi nginx:1.27
$ docker inspect web                                  # full JSON detail
$ docker stats                                        # live resource usage
$ docker system prune -a                              # remove unused stuff (careful)

Useful run flags:

Flag Meaning
-d Detached (background)
-it Interactive with a TTY
--rm Remove when it exits
-p host:container Publish a port
-e KEY=value / --env-file Environment variables
-v name:/path / --mount Attach a volume or bind mount
--network Choose a network
--restart unless-stopped Restart policy: no, on-failure, always, unless-stopped
--memory 512m --cpus 1.5 Resource limits
-u 1000:1000 Run as non-root

Dockerfile

Instructions to build an image.

FROM python:3.12-slim

WORKDIR /app

# copy dependency list first so this layer is cached until it changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV PORT=8000
EXPOSE 8000

USER 1000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
Instruction Notes
FROM Base image. Start of a stage
WORKDIR Set the working directory
COPY / ADD Copy files in (ADD also extracts tars and fetches URLs, prefer COPY)
RUN Execute at build time, creates a layer
ENV Environment variable (build and run)
ARG Build-time variable only
EXPOSE Documents the port, does not publish it
USER Drop root
ENTRYPOINT The executable
CMD Default args / command, overridable at docker run
HEALTHCHECK Command Docker runs to mark container healthy
VOLUME Declare a mount point

CMD vs ENTRYPOINT: ENTRYPOINT ["python"] + CMD ["app.py"] runs python app.py; docker run img other.py replaces only the CMD.

Prefer the exec form ["cmd", "arg"] over shell form so signals (SIGTERM) reach your process.

Build

$ docker build -t myapp:1.0 .
$ docker build -t myapp:1.0 -f Dockerfile.prod --build-arg VERSION=1.0 .
$ docker tag myapp:1.0 myregistry/myapp:1.0
$ docker push myregistry/myapp:1.0

Layer cache rules

  • Order instructions from least to most frequently changing
  • A changed layer invalidates every layer after it
  • Combine related RUNs, clean caches in the same layer (apt-get install ... && rm -rf /var/lib/apt/lists/*)

.dockerignore

Keeps the build context small and secrets out of images.

.git
node_modules
__pycache__
.env
*.log

Multi-stage builds

Build with a big toolchain, ship only the result. Smaller and safer images.

# build stage
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

# runtime stage
FROM gcr.io/distroless/static
COPY --from=build /out/app /app
USER nonroot
ENTRYPOINT ["/app"]
$ docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0 --push .

Storage

Container filesystems are ephemeral. Persist data outside the writable layer.

Type Command Use
Volume -v pgdata:/var/lib/postgresql/data Managed by Docker, best for databases
Bind mount -v $(pwd):/app Map a host path, great for development
tmpfs --tmpfs /tmp In-memory, never touches disk
$ docker volume create pgdata
$ docker volume ls
$ docker volume inspect pgdata
$ docker run -d -v pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=pw postgres:16

Networking

Driver Behaviour
bridge (default) Private network on the host; containers get internal IPs, publish ports to reach out
user-defined bridge Same, plus DNS by container name and better isolation
host Share the host network stack, no port mapping
none No networking
overlay Multi-host networking (Swarm)
macvlan Container appears as a device on the physical LAN
$ docker network create appnet
$ docker run -d --name db --network appnet postgres:16
$ docker run -d --name api --network appnet -p 8000:8000 myapp:1.0
# inside "api", the database is reachable at hostname "db"

-p 8080:80 maps host port 8080 to container port 80 using iptables NAT; see Port Numbers.


Image anatomy, registries and the full range of build tools (BuildKit, Podman / Buildah, Kaniko, Buildpacks, Jib) are in 3. Container Images and Build Tools. Where images end up running is in Orchestration Platforms.


Image Hygiene and Security

  • Use small, specific base images (-slim, alpine, distroless) and pin versions; avoid latest in production
  • Do not run as root: USER
  • No secrets in images or ENV: use runtime secrets, env injection, Docker/Compose secrets, or a secrets manager
  • Scan images: docker scout, Trivy, ECR / ACR scanning
  • Read-only filesystem (--read-only), drop capabilities (--cap-drop ALL), --security-opt no-new-privileges
  • Sign and verify images (Docker Content Trust, Cosign)
  • Keep the Docker socket (/var/run/docker.sock) away from containers; it is root on the host

Example: A Small Multi-Stage Image

sharifndlovu123/server_performance_stats is a shell script that prints memory, CPU and disk usage, packaged with a multi-stage Dockerfile: a builder stage installs mpstat (from sysstat) and free (from procps), which the slim Python base image lacks, and the final stage copies over only what it needs. Run it with docker run --rm perf-stats, or docker run --rm -t perf-stats true for the 5 second watch mode.


Debugging Checklist

  1. docker ps -a: did it exit? check exit code (137 = killed/OOM, 139 = segfault, 1 = app error)
  2. docker logs <c>
  3. docker inspect <c>: env, mounts, network, health
  4. docker exec -it <c> sh
  5. Run the image interactively with a shell: docker run --rm -it --entrypoint sh myapp:1.0
  6. Port not reachable? App must listen on 0.0.0.0, not 127.0.0.1, and the port must be published

Cloud Services Using Docker Images

  • AWS: ECR, ECS, Fargate, EKS, App Runner, Elastic Beanstalk
  • Azure: ACR, App Service, Container Apps, ACI, AKS
  • Kubernetes runs the same OCI images as Pods

See 3. AWS Compute and 2. Azure Compute.