Skip to content

Docker

On this page

Containers from the ground up: images vs containers, the Dockerfile, networking and volumes, Compose for multi-service apps, and the practices that keep images small and safe.

Basics

Docker packages an application and its dependencies into a container — an isolated, portable process that shares the host kernel. Containers use Linux namespaces (isolation) and cgroups (resource limits), so they're far lighter than VMs.

Image vs container

  • Image — an immutable, layered template (read-only). Built from a Dockerfile.
  • Container — a running (or stopped) instance of an image, with a thin writable layer on top.
  • Registry — stores/distributes images (Docker Hub, GHCR, ECR, GCR, Harbor).
Dockerfile ──build──▶ Image ──run──▶ Container
                       │
                     push/pull
                       ▼
                    Registry

Architecture

The Docker CLI talks to the Docker daemon (dockerd), which manages images, containers, networks, and volumes via containerd and runc (OCI runtime). Images and the runtime follow OCI standards.

Storage & networking

  • Volumes — Docker-managed persistent storage (/var/lib/docker/volumes). Survive container removal.
  • Bind mounts — map a host path into the container.
  • tmpfs — in-memory, ephemeral.
  • Networks — bridge (default, single host), host (share host net), none, overlay (multi-host/Swarm). Containers on the same user-defined bridge resolve each other by name.

Cheatsheet

Images & containers

docker build -t app:1.0 .
docker images ; docker pull nginx:alpine
docker run -d --name web -p 8080:80 nginx:alpine
docker run -it --rm ubuntu bash        # interactive, auto-remove
docker ps ; docker ps -a               # running / all
docker logs -f web
docker exec -it web sh                 # shell into a running container
docker stop web ; docker start web ; docker rm web
docker rmi nginx:alpine
docker stats                           # live resource usage
docker inspect web                     # full JSON metadata

Volumes & networks

docker volume create data
docker run -v data:/var/lib/mysql mysql:8
docker run -v $(pwd):/app -w /app node:20 npm test   # bind mount
docker network create appnet
docker run --network appnet --name db postgres:16

Cleanup (reclaim space)

docker system df                       # what's using disk
docker container prune                 # remove stopped
docker image prune -a                  # remove unused images
docker system prune -a --volumes       # nuke unused everything (careful)

Registry

docker login ghcr.io
docker tag app:1.0 ghcr.io/moin/app:1.0
docker push ghcr.io/moin/app:1.0

Dockerfile (multi-stage, small & cached)

# 1) build stage
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download           # cached unless deps change
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server

# 2) minimal runtime
FROM gcr.io/distroless/static
COPY --from=build /app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]

docker compose (compose.yaml)

services:
  web:
    build: .
    ports: ["8080:8080"]
    environment:
      - DB_HOST=db
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - dbdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5
volumes:
  dbdata:
docker compose up -d ; docker compose logs -f ; docker compose down
docker compose down -v                 # also remove volumes

Thumb Rules

Rules of thumb

  • One concern per container. A container should run one main process (PID 1).
  • Containers are ephemeral. Persist anything important in volumes/databases, not the container layer.
  • Order Dockerfile from least- to most-frequently-changing to maximize layer cache (deps before code).
  • Pin image tags / use digests. latest is not reproducible.
  • Smaller is better and safer — use alpine/slim/distroless, multi-stage builds.
  • Don't run as root inside the container unless you must.
  • Don't bake secrets into images. Use env/secret managers at runtime.
  • Use .dockerignore to keep build context (and image) lean.
  • A container exits when PID 1 exits — keep the foreground process running.

Use Cases

  • Consistent dev/prod parity — “works on my machine” disappears.
  • Microservices — package and ship each service independently.
  • CI/CD — reproducible build/test environments.
  • Packaging tools/CLIs — distribute as images.
  • Local stacks — spin up DB + cache + app with Compose.
  • Foundation for Kubernetes — K8s schedules OCI containers.

Common Issues

Container exits immediately

The main process finished or crashed. Check docker logs <name>. The foreground process must stay running; a shell with no command, or a daemon that forks, will exit.

Port not reachable

You didn't publish it (-p host:container), the app binds 127.0.0.1 instead of 0.0.0.0, or a host firewall blocks it. Verify with docker ps (PORTS column).

Data disappeared after docker rm

It lived in the writable container layer. Use a named volume or bind mount for anything that must persist.

Image is huge / build is slow

No multi-stage build, copying everything (missing .dockerignore), or cache busted by COPY . . before installing deps. Reorder and slim the base image.

“no space left on device”

Dangling images/volumes/build cache piled up. docker system df then docker system prune (and --volumes carefully).

Permission denied on bind mount

Host vs container UID/GID mismatch, or SELinux. Match the user, set :z/:Z on SELinux hosts, or adjust ownership.

Networking between containers fails

On the default bridge, name resolution isn't automatic. Put them on a user-defined network so they can reach each other by service/container name.

Best Practices

  • Multi-stage builds + minimal base images (distroless/alpine/slim).
  • Run as non-root, set a read-only root FS where possible, drop capabilities.
  • Pin versions/digests; rebuild regularly for security patches.
  • Scan images (Trivy, Grype, docker scout) and use trusted base images.
  • Externalize config & secrets (env vars, secret managers), never in the image.
  • Add HEALTHCHECK so orchestrators know real readiness.
  • Set resource limits (--memory, --cpus) to protect the host.
  • Use .dockerignore and keep build context small.
  • One process per container; use Compose/K8s for multi-service apps.

Official Sources