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; avoidlatestin 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
docker ps -a: did it exit? check exit code (137= killed/OOM,139= segfault,1= app error)docker logs <c>docker inspect <c>: env, mounts, network, healthdocker exec -it <c> sh- Run the image interactively with a shell:
docker run --rm -it --entrypoint sh myapp:1.0 - Port not reachable? App must listen on
0.0.0.0, not127.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.