← Tutorials 🤖 AI 📓 Architect's Notebook ☁ AWS 📋 Cheatsheet 🗃 Databases 🚀 DevOps 🧩 DSA ❓ FAQ 🖥 Frontend ☕ Java 🍏 MongoDB 🐍 Python 🌿 Spring Boot 🏗 System Design 🏛 TOGAF
TutorialsDevOps › Docker

Introduction to Docker

🐳 Docker · Foundations · 6 min read

Docker is an open-source platform for building, shipping, and running applications inside containers — lightweight, isolated, portable units that bundle your app together with everything it needs to run (code, runtime, libraries, config). The promise: "build once, run anywhere."

The Problem It Solves

"It works on my machine" — an app runs in development but breaks in production because of different OS versions, missing libraries, or config drift. Docker eliminates this by packaging the app and its entire environment into a single image that runs identically everywhere.

Key Benefits

  • Consistency — same environment from laptop to production.
  • Isolation — each container is sandboxed; dependencies don't clash.
  • Lightweight — containers share the host kernel, so they start in milliseconds and use far less than a VM.
  • Portability — runs on any machine with a container runtime (laptop, server, cloud).
  • Scalability — spin up many identical containers behind a load balancer.

Core Vocabulary

TermMeaning
ImageA read-only template (blueprint) used to create containers
ContainerA running instance of an image
DockerfileA script of instructions to build an image
RegistryA store for images (e.g. Docker Hub)
VolumePersistent storage that outlives a container

Your First Container

# pull and run — prints a hello message, then exits docker run hello-world # run an Nginx web server in the background, mapped to port 8080 docker run -d -p 8080:80 nginx
💡 Docker (2013) popularized containers, but the underlying tech (Linux namespaces + cgroups) predates it. Docker made it usable with a simple CLI, image format, and registry.

Containers vs Virtual Machines

🐳 Docker · Foundations · 6 min read

Both isolate workloads, but at very different layers. A VM virtualizes hardware and runs a full guest OS; a container virtualizes the operating system and shares the host kernel.

Side by Side — Visual

Virtual Machines App ABins/LibsGuest OS App BBins/LibsGuest OS App CBins/LibsGuest OS Hypervisor Host OS Physical Server Containers App ABins/Libs App BBins/Libs App CBins/Libs Docker Engine Host OS (shared kernel) Physical Server

Comparison

Virtual MachineContainer
Isolation levelHardware (full guest OS)OS process (shared kernel)
SizeGBsMBs
StartupMinutesMilliseconds–seconds
OverheadHigh (each runs an OS)Low
Isolation strengthStronger (separate kernel)Weaker (shared kernel)
Density per hostTensHundreds–thousands
💡 They're complementary, not rivals. In the cloud you often run containers inside VMs — the VM gives hardware-level isolation between tenants, containers give density and speed within it.

Docker Architecture

🐳 Docker · Foundations · 7 min read

Docker uses a client–server architecture. The Docker CLI (client) talks to the Docker daemon (server) over a REST API; the daemon does the heavy lifting of building, running, and managing containers, pulling images from registries as needed.

The Moving Parts — Visual

Docker Clientdocker build/run/pull Docker Host Docker Daemon (dockerd) Images Containers RegistryDocker Hub REST API pull/push

Components

Docker Client

The CLI you type into. Sends commands to the daemon via the REST API (local socket or remote).

Docker Daemon (dockerd)

The background service that builds images, runs containers, and manages networks/volumes.

Images & Containers

The daemon stores images and runs them as containers on the host.

Registry

Remote store of images. The daemon pulls images you don't have and can push your own.

Under the Hood

The daemon delegates to containerd (high-level runtime) which uses runc (low-level runtime) to actually create containers using Linux namespaces (isolation) and cgroups (resource limits).

💡 When you run docker run, the client just sends an API request — all the real work (pull image, create namespaces, start process) happens in the daemon.

Docker Engine

🐳 Docker · Foundations · 5 min read

Docker Engine is the core product — the client-server application that creates and runs containers. It is what you install on a machine to "have Docker."

Three Parts

  • Docker Daemon (dockerd) — long-running server process managing Docker objects.
  • REST API — the interface programs use to talk to the daemon.
  • Docker CLI (docker) — the command-line client that calls the API.

Editions

EditionFor
Docker Engine (CE)The free open-source engine — typically on Linux servers
Docker DesktopMac/Windows GUI bundling the engine in a lightweight VM + extras (Compose, Kubernetes)

Checking It

# version of client + engine docker version # system-wide info: containers, images, storage driver, kernel docker info
💡 On macOS/Windows there is no native Linux kernel, so Docker Desktop runs the engine inside a small Linux VM. On Linux the engine runs directly on the host kernel — which is why Linux containers are most efficient there.

Docker Images

🐳 Docker · Images & Registries · 6 min read

A Docker image is a read-only template that contains everything needed to run an application — filesystem, dependencies, and metadata (default command, env, exposed ports). Containers are running instances of images.

Image Naming

Format: [registry/]repository[:tag] — e.g. docker.io/library/nginx:1.27. If no tag is given, Docker assumes :latest.

Working with Images

# download an image from a registry docker pull nginx:1.27 # list local images docker images # remove an image docker rmi nginx:1.27 # tag an image for your own repo docker tag nginx:1.27 myrepo/nginx:prod # clean up unused images docker image prune -a

Images Are Immutable

You never edit an image — you build a new one. Each image is identified by a content digest (sha256:...), guaranteeing the exact same bytes everywhere.

💡 Pin a specific tag or digest in production (nginx:1.27, not nginx:latest). latest is just a default tag, not "the newest" — relying on it makes builds non-reproducible.

Docker Image Layers

🐳 Docker · Images & Registries · 6 min read

Images are built in layers — each instruction in a Dockerfile creates a new read-only layer stacked on the previous one. Layers are cached and shared between images, which makes builds fast and storage efficient.

Layered Filesystem — Visual

Container layer (read-write, thin) CMD ["java","-jar","app.jar"] (metadata) COPY app.jar → layer RUN apt-get install … → layer FROM eclipse-temurin:21-jre (base layers) Base OS layer (read-only) read-only image layers (shared)

How Layers Help

  • Build cache — unchanged layers are reused; only changed layers and those after them rebuild.
  • Storage sharing — ten images on the same base share that base layer once on disk.
  • Faster pulls — only missing layers are downloaded.
  • The running container adds a thin writable layer on top (copy-on-write).

Inspecting Layers

# show the layer-by-layer history & size of an image docker history myapp:latest
💡 Order Dockerfile instructions from least- to most-frequently-changing. Put COPY pom.xml + dependency download before COPY src — your dependency layer stays cached across code changes.

Docker Hub

🐳 Docker · Images & Registries · 5 min read

Docker Hub is the default public registry — a cloud library of images. It hosts official images (nginx, postgres, node, eclipse-temurin), verified publisher images, and personal/team repositories.

Common Operations

# log in docker login # pull (default registry is Docker Hub, so no host prefix needed) docker pull postgres:16 # push your own image (repo must be you/<name>) docker tag myapp:latest yourusername/myapp:1.0 docker push yourusername/myapp:1.0

Image Trust Tiers

TierMeaning
Official ImagesCurated by Docker (e.g. nginx, postgres) — best starting point
Verified PublisherFrom trusted vendors
CommunityAnyone — vet before using in production
⚠️ Free Docker Hub pulls are rate-limited for anonymous users. In CI/CD, log in or use a mirror/private registry to avoid toomanyrequests errors.

Docker Registry

🐳 Docker · Images & Registries · 5 min read

A registry is a storage and distribution system for Docker images, organized into repositories (each holding tagged versions of an image). Docker Hub is the best-known public registry; many others exist.

Registry vs Repository vs Tag

  • Registry — the server hosting images (Docker Hub, ECR, GHCR).
  • Repository — a named collection of related images (yourusername/myapp).
  • Tag — a specific version within a repo (:1.0, :latest).

Popular Registries

RegistryProvider
Docker HubDocker (default public)
Amazon ECRAWS
GitHub Container Registry (GHCR)GitHub
Google Artifact RegistryGCP
Azure Container Registry (ACR)Azure
💡 The push/pull workflow is identical across registries — only the image name prefix changes (e.g. 123.dkr.ecr.us-east-1.amazonaws.com/myapp for ECR).

Private Docker Registries

🐳 Docker · Images & Registries · 6 min read

A private registry stores images that shouldn't be public — proprietary apps, internal base images. You can run your own or use a managed cloud registry.

Run Your Own Registry

# run the official registry image locally on port 5000 docker run -d -p 5000:5000 --name registry --restart=always registry:2 # tag & push to it docker tag myapp:latest localhost:5000/myapp:1.0 docker push localhost:5000/myapp:1.0 # pull from it docker pull localhost:5000/myapp:1.0

Production Considerations

  • TLS — registries must use HTTPS (or be configured as an insecure registry — avoid in prod).
  • Authentication — token/basic auth; integrate with SSO for teams.
  • Storage backend — back it with S3/GCS for durability.
  • Managed options — ECR, GHCR, ACR, Harbor (adds scanning, RBAC, replication).
💡 Most teams skip self-hosting and use a managed private registry (ECR/GHCR/ACR) — you get auth, vulnerability scanning, and lifecycle policies without operating the registry yourself. Harbor is the popular self-hosted choice when you need full control.

Docker Containers

🐳 Docker · Containers · 6 min read

A container is a running (or stopped) instance of an image — an isolated process with its own filesystem, network, and resource limits, sharing the host kernel. You can run many containers from the same image.

Running Containers

# run interactively (-it) and remove on exit (--rm) docker run -it --rm ubuntu bash # run detached (-d), name it, map a port docker run -d --name web -p 8080:80 nginx # list running / all containers docker ps docker ps -a # stop, start, restart, remove docker stop web docker start web docker rm web

Key Run Flags

FlagPurpose
-dDetached (run in background)
-itInteractive + TTY (shell access)
-p host:containerPublish a port
-e KEY=valSet an environment variable
-v vol:/pathMount a volume
--nameGive the container a name
--rmAuto-remove when it exits
💡 A container lives only as long as its main process (PID 1). When that process exits, the container stops. That's why docker run ubuntu exits immediately — bash with no TTY has nothing to do.

Container Lifecycle

🐳 Docker · Containers · 6 min read

A container moves through well-defined states. Understanding them is key to debugging "why did my container exit?"

State Machine — Visual

Created Running Paused Stopped Removed start pause stop/exit start rm

Commands by Transition

CommandTransition
docker create→ Created (not started)
docker start / run→ Running
docker pause / unpauseRunning ↔ Paused
docker stop (SIGTERM→SIGKILL)Running → Stopped (graceful)
docker kill (SIGKILL)Running → Stopped (forced)
docker rmStopped → Removed
💡 docker stop sends SIGTERM, waits (default 10s), then SIGKILL — so your app can shut down gracefully. docker kill skips straight to SIGKILL. Handle SIGTERM in your app to finish in-flight work.

Docker Restart Policies

🐳 Docker · Containers · 5 min read

Restart policies tell Docker how to react when a container exits or the daemon restarts — essential for keeping services running without an external orchestrator.

The Four Policies

PolicyBehavior
noNever restart (default)
on-failure[:max]Restart only on non-zero exit, optionally capped
alwaysAlways restart; also starts on daemon boot
unless-stoppedLike always, but not if you manually stopped it

Usage

# keep a web server alive across crashes and reboots docker run -d --restart=unless-stopped -p 8080:80 nginx # update an existing container's policy docker update --restart=on-failure:5 web
💡 For a single host, unless-stopped is the pragmatic default for long-running services. In Kubernetes/Swarm the orchestrator handles restarts instead, so policies matter less there.

Dockerfile

🐳 Docker · Building Images · 7 min read

A Dockerfile is a text file of instructions that Docker reads to build an image automatically and reproducibly. Each instruction adds a layer.

Core Instructions

InstructionPurpose
FROMBase image to start from
WORKDIRSet the working directory
COPY / ADDCopy files into the image
RUNExecute a command at build time (creates a layer)
ENVSet environment variables
EXPOSEDocument the listening port
ENTRYPOINTThe fixed executable to run
CMDDefault args / command (overridable)

Example — Spring Boot

# Dockerfile FROM eclipse-temurin:21-jre WORKDIR /app COPY target/app.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"]

ENTRYPOINT vs CMD

  • ENTRYPOINT — the executable that always runs.
  • CMD — default arguments, easily overridden at docker run.
  • Common combo: ENTRYPOINT ["java","-jar","app.jar"] + CMD ["--spring.profiles.active=prod"].
💡 Prefer COPY over ADD (ADD has surprising behavior with URLs/tar extraction). Use the exec form ["cmd","arg"] not shell form — it handles signals correctly so SIGTERM reaches your app.

Docker Build Process

🐳 Docker · Building Images · 6 min read

docker build reads the Dockerfile, sends the build context (the directory) to the daemon, and executes each instruction, caching layers along the way.

Build Flow — Visual

Dockerfile +context docker build layer-by-layeruse cache if unchanged Image

Commands

# build, tagging the result; "." is the build context docker build -t myapp:1.0 . # build with no cache (force fresh) docker build --no-cache -t myapp:1.0 . # build a specific target in a multi-stage file docker build --target=builder -t myapp:build .

The .dockerignore File

Like .gitignore — exclude files from the build context (target/, .git, node_modules) to speed up builds and shrink images.

# .dockerignore .git target/* !target/app.jar *.md
💡 The build context is everything in the directory you pass — a huge context (e.g. node_modules or .git) is slow to send to the daemon. A good .dockerignore is the easiest build speedup.

Multi-Stage Builds

🐳 Docker · Building Images · 7 min read

A multi-stage build uses multiple FROM statements. You build the app in a heavy "builder" stage (with the full JDK/Maven), then copy only the final artifact into a slim runtime stage — leaving build tools behind. Result: dramatically smaller, more secure images.

Two Stages — Visual

Stage 1: builder (JDK + Maven) mvn package → app.jar ~700 MB · thrown away Stage 2: runtime (JRE only) COPY --from=builder app.jar ~200 MB · shipped ✓ copy artifact

Example

# ---- build stage ---- FROM maven:3.9-eclipse-temurin-21 AS builder WORKDIR /src COPY pom.xml . RUN mvn dependency:go-offline # cached unless pom changes COPY src ./src RUN mvn package -DskipTests # ---- runtime stage ---- FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=builder /src/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"]
💡 Multi-stage builds are the single biggest win for image size and security: the final image contains no compiler, no Maven, no source — just the JRE and your jar.

Docker Image Optimisation

🐳 Docker · Building Images · 6 min read

Smaller images pull faster, deploy faster, and have a smaller attack surface. Several techniques compound.

Techniques

  • Slim/distroless baseeclipse-temurin:21-jre over :21 (JDK); alpine or distroless for minimal footprint.
  • Multi-stage builds — leave build tools behind.
  • Layer ordering — copy dependencies before source so the dep layer caches.
  • Combine RUN steps — chain with && and clean up in the same layer (rm -rf /var/lib/apt/lists/*).
  • .dockerignore — keep junk out of the context.
  • Specific tags — reproducible and cacheable.

Cleaning in One Layer

# BAD — cleanup in a separate layer doesn't shrink the image RUN apt-get update && apt-get install -y curl RUN rm -rf /var/lib/apt/lists/* # GOOD — install + clean in one layer RUN apt-get update && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/*

Measure It

docker images myapp # see size docker history myapp:1.0 # see which layers are heavy
💡 A naive Spring Boot image can be 700 MB+; multi-stage + JRE base brings it to ~200 MB; layered jars + jlink/distroless can push it under 100 MB.

Docker Best Practices

🐳 Docker · Building Images · 6 min read

Battle-tested rules for building production-grade images and containers.

✓ Do

  • Use official, minimal, pinned base images
  • Use multi-stage builds
  • One process / concern per container
  • Run as a non-root user
  • Externalize config via env vars
  • Add a HEALTHCHECK
  • Use .dockerignore + good layer order
  • Handle SIGTERM for graceful shutdown

✗ Avoid

  • :latest in production
  • Running as root
  • Storing secrets in the image/ENV in the Dockerfile
  • Multiple services in one container
  • Storing persistent data inside the container
  • Huge build contexts
  • Installing unnecessary packages

Run as Non-Root

FROM eclipse-temurin:21-jre RUN useradd -r -u 1001 appuser USER appuser COPY --chown=appuser target/app.jar /app/app.jar ENTRYPOINT ["java","-jar","/app/app.jar"]
💡 The golden rule: immutable, minimal, single-purpose, non-root, externally-configured. If your container needs editing at runtime, you're holding it wrong — rebuild the image instead.

Docker CLI Commands

🐳 Docker · CLI & Operations · 7 min read

The docker CLI is your main interface. Here's the everyday toolkit grouped by what you're working with.

Images

docker pull <image> # download docker build -t name:tag . # build docker images # list docker rmi <image> # remove docker tag src target # retag docker push <image> # upload

Containers

docker run [opts] <image> # create + start docker ps -a # list all docker stop / start / restart <c> docker rm <c> # remove docker logs -f <c> # view logs docker exec -it <c> bash # shell in

System & Cleanup

docker system df # disk usage docker system prune -a # remove unused images/containers/networks docker volume prune # remove unused volumes docker network ls # list networks
💡 Modern Docker groups commands under management nouns too: docker container ls, docker image ls, docker volume ls. The short forms (docker ps, docker images) are aliases.

Docker Logs

🐳 Docker · CLI & Operations · 5 min read

Docker captures whatever a container writes to stdout/stderr and exposes it via docker logs. This is why 12-factor apps log to stdout rather than files.

Commands

docker logs web # all logs docker logs -f web # follow (tail -f) docker logs --tail 100 web # last 100 lines docker logs --since 10m web # last 10 minutes docker logs -t web # with timestamps

Logging Drivers

By default Docker uses the json-file driver. For production, ship logs centrally with drivers like journald, fluentd, awslogs, or gelf.

# cap log size to avoid filling the disk docker run -d --log-opt max-size=10m --log-opt max-file=3 myapp
⚠️ The default json-file driver grows unbounded — a chatty container can fill the host disk. Always set max-size/max-file or use a centralized driver in production.

Docker Exec

🐳 Docker · CLI & Operations · 4 min read

docker exec runs a new command inside a running container — the primary way to debug a live container (open a shell, inspect files, check processes).

Usage

# interactive shell inside a running container docker exec -it web bash docker exec -it web sh # for alpine/slim images without bash # run a one-off command docker exec web ls /app docker exec web env # inspect environment variables # run as a specific user docker exec -u root -it web bash
💡 exec runs in an already-running container; run starts a new one. If a container has already exited you can't exec into it — check docker logs instead, or start it with an overridden command.

Docker Inspect

🐳 Docker · CLI & Operations · 5 min read

docker inspect returns detailed low-level JSON metadata about any Docker object — containers, images, networks, volumes. Indispensable for debugging configuration.

Usage

docker inspect web # full JSON # extract a specific field with a Go template docker inspect -f '{{.State.Status}}' web docker inspect -f '{{.NetworkSettings.IPAddress}}' web docker inspect -f '{{json .Config.Env}}' web # inspect a network to see connected containers docker network inspect bridge

What You Can Find

  • State (running, exit code, start/finish time, restart count)
  • Network settings (IP, ports, networks)
  • Mounts (volumes, bind mounts)
  • Config (env vars, entrypoint, command, labels)
💡 docker inspect -f '{{.State.ExitCode}}' <c> tells you why a container died — exit code 137 = SIGKILL (often OOM), 143 = SIGTERM, 1 = app error.

Docker Stats

🐳 Docker · CLI & Operations · 4 min read

docker stats streams live resource usage per container — CPU, memory, network, and disk I/O — a quick way to spot a runaway container.

Usage

# live stream for all running containers docker stats # one-shot snapshot (no stream) docker stats --no-stream # specific containers, custom columns docker stats web db --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

What to Watch

MetricWatch for
CPU %Sustained 100% → CPU-bound or a loop
MEM USAGE / LIMITNear the limit → risk of OOM kill (137)
NET I/OUnexpected spikes
BLOCK I/OHeavy disk reads/writes
💡 docker stats is great for ad-hoc checks but isn't historical. For trends and alerts, use cAdvisor + Prometheus + Grafana (see Container Monitoring).

Docker Health Checks

🐳 Docker · CLI & Operations · 6 min read

A HEALTHCHECK tells Docker how to test that a container is actually working — not just running. Orchestrators use health status to restart or stop routing traffic to unhealthy containers.

In a Dockerfile

HEALTHCHECK --interval=30s --timeout=3s --retries=3 --start-period=40s \ CMD curl -f http://localhost:8080/actuator/health || exit 1

Parameters

OptionMeaning
--intervalTime between checks
--timeoutMax time a check may take
--retriesConsecutive failures → unhealthy
--start-periodGrace window during slow startup

States & Checking

A container is startinghealthy or unhealthy.

docker ps # STATUS shows (healthy) docker inspect -f '{{.State.Health.Status}}' web
💡 For Spring Boot, point the healthcheck at /actuator/health (enable spring-boot-starter-actuator). Use --start-period generously — JVM apps take seconds to warm up and you don't want false "unhealthy" flaps at boot.

Docker Volumes

🐳 Docker · Storage · 6 min read

Containers are ephemeral — when removed, their writable layer is gone. Volumes provide persistent storage that lives independently of containers, so data (databases, uploads) survives container restarts and replacements.

Three Storage Options — Visual

Container Named VolumeDocker-managed (preferred) Bind Mounthost path → container tmpfsin-memory, ephemeral

Working with Named Volumes

docker volume create pgdata docker run -d -v pgdata:/var/lib/postgresql/data postgres:16 docker volume ls docker volume inspect pgdata docker volume rm pgdata

Volumes vs Bind Mounts vs tmpfs

TypeStoredUse for
Named VolumeDocker-managed areaDatabases, app data (production)
Bind MountAny host pathLocal dev (live code), config files
tmpfsHost RAMSecrets/scratch, never persisted
💡 Prefer named volumes in production: they're portable, managed by Docker, easy to back up, and decoupled from the host's directory layout.

Bind Mounts

🐳 Docker · Storage · 5 min read

A bind mount maps a specific host directory or file directly into a container. Changes on either side are instantly visible on the other — perfect for development.

Usage

# mount current dir into the container (live code reload in dev) docker run -v $(pwd):/app -w /app node:20 npm run dev # mount a single config file, read-only docker run -v /etc/myapp/config.yml:/app/config.yml:ro myapp # explicit --mount syntax (clearer) docker run --mount type=bind,source="$(pwd)",target=/app myapp

Pros & Cons

✓ Good for

  • Live code editing in dev
  • Injecting host config files
  • Sharing build output

✗ Watch out

  • Ties container to host paths (not portable)
  • Permission/ownership mismatches
  • Security: container can touch host files
💡 The :ro suffix mounts read-only — use it for config and secrets so a compromised container can't modify host files.

Named Volumes

🐳 Docker · Storage · 5 min read

Named volumes are persistent storage fully managed by Docker (under /var/lib/docker/volumes). You refer to them by name, not by host path — making them portable and the recommended choice for stateful data.

Lifecycle

docker volume create appdata # create docker run -d -v appdata:/data myapp # use (auto-created if missing) docker volume ls # list docker volume inspect appdata # details + mountpoint docker volume prune # remove unused

Backing Up a Volume

# tar the volume's contents to the host via a throwaway container docker run --rm -v appdata:/data -v $(pwd):/backup alpine \ tar czf /backup/appdata-backup.tar.gz -C /data .
💡 Named volumes survive docker rm of the container — that's the point. They're only deleted when you explicitly docker volume rm them (or docker run --rm -v with anonymous volumes).

Docker Storage Drivers

🐳 Docker · Storage · 5 min read

Storage drivers control how Docker stacks and manages image layers and the container's writable layer on disk, using a copy-on-write (CoW) strategy.

Common Drivers

DriverNotes
overlay2Default & recommended on modern Linux — efficient, stable
fuse-overlayfsFor rootless Docker
btrfs / zfsUsed when the host filesystem is btrfs/zfs
devicemapper (legacy)Older systems — avoid for new setups

Copy-on-Write

Image layers are read-only and shared. When a container modifies a file, the driver copies it up into the container's writable layer first — so the underlying image stays intact and is shared across containers.

docker info | grep "Storage Driver" # check your driver
💡 You rarely change the storage driver — overlay2 is the right default almost everywhere. Knowing CoW matters because heavy writes to the container layer are slower than writing to a volume — another reason databases belong on volumes.

Docker Networking

🐳 Docker · Networking · 7 min read

Docker networking lets containers talk to each other, to the host, and to the outside world. Docker creates virtual networks and connects containers to them; containers on the same user-defined network can reach each other by name via built-in DNS.

Network Types — Visual

bridge (default)isolated virtual net app db talk via DNS names hostshares host's network stack no isolation, fastest overlayspans multiple hosts host A host B

Built-in Network Types

TypeUse
bridgeDefault — isolated network on a single host
hostContainer shares the host's network (no isolation)
noneNo networking
overlayMulti-host networking (Swarm/Kubernetes)
macvlanContainer gets its own MAC/IP on the physical network

Commands

docker network create mynet docker run -d --network mynet --name db postgres docker run -d --network mynet --name app myapp # app can reach "db" by name docker network ls docker network inspect mynet
💡 Create a user-defined bridge network (not the default bridge) for multi-container apps — only user-defined networks give automatic container-name DNS resolution.

Bridge Network

🐳 Docker · Networking · 5 min read

The bridge is the default network driver. Docker creates a private internal network on the host (docker0); containers attached to it get an internal IP and communicate through the bridge, isolated from the host's other networks.

Default vs User-Defined Bridge

Default bridgeUser-defined bridge
DNS by nameNo (IP only / legacy --link)Yes (container names resolve)
IsolationShared by all default containersScoped to attached containers
RecommendedNoYes

Example

docker network create app-net docker run -d --network app-net --name redis redis docker run -d --network app-net --name api myapi # inside "api", the hostname "redis" resolves automatically
💡 Containers on a user-defined bridge reach each other by name and port (redis:6379) — no need to publish ports to the host for internal traffic. Only publish (-p) what the outside world needs.

Host Network

🐳 Docker · Networking · 4 min read

With --network host, the container shares the host's network stack directly — no isolation, no NAT, no port mapping. The container's ports are the host's ports.

Usage

# app binds directly to host port 8080 — no -p needed docker run -d --network host myapp

Trade-offs

✓ Pros

  • Best network performance (no NAT overhead)
  • Useful for high-throughput / many-ports apps

✗ Cons

  • No network isolation
  • Port conflicts with the host
  • Linux-only (behaves differently on Docker Desktop)
⚠️ Host networking removes a key security boundary and only works as expected on Linux. Use it only when you genuinely need the performance — bridge + port mapping is the safer default.

Overlay Network

🐳 Docker · Networking · 5 min read

An overlay network connects containers running on different Docker hosts as if they were on one network. It's the foundation of multi-host orchestration (Docker Swarm), encapsulating traffic (VXLAN) across the physical network.

How It Works

  • Spans multiple Docker daemons in a Swarm cluster.
  • Containers/services on the same overlay communicate by name across hosts.
  • Traffic is encapsulated and optionally encrypted.

Creating One (Swarm)

docker swarm init # enable Swarm mode docker network create -d overlay --attachable appnet docker service create --name web --network appnet -p 80:80 nginx
💡 You need Swarm mode (or Kubernetes' equivalent CNI) for overlay networks — on a single host, a user-defined bridge is all you need. Overlay = "bridge, but across machines."

Port Mapping

🐳 Docker · Networking · 5 min read

By default a container's ports are only reachable inside its network. Port mapping (publishing) exposes a container port on the host so the outside world can reach it.

Syntax: -p host:container

docker run -d -p 8080:80 nginx # host 8080 → container 80 docker run -d -p 80:80 -p 443:443 nginx docker run -d -p 127.0.0.1:5432:5432 postgres # bind to localhost only docker run -d -P nginx # publish all EXPOSEd ports to random host ports docker port web # show the mappings

EXPOSE vs -p

  • EXPOSE in a Dockerfile is documentation — it doesn't publish anything.
  • -p at runtime actually publishes the port to the host.
💡 Bind databases to 127.0.0.1: (e.g. -p 127.0.0.1:5432:5432) so they're reachable locally for debugging but not exposed to the whole network/internet.

Environment Variables

🐳 Docker · Networking · 5 min read

Environment variables are the standard way to configure containers without rebuilding the image — the 12-factor "config in the environment" principle.

Setting Them

# single vars docker run -e SPRING_PROFILES_ACTIVE=prod -e DB_HOST=db myapp # from a file docker run --env-file .env myapp
# .env file SPRING_PROFILES_ACTIVE=prod DB_HOST=db DB_PASSWORD=secret

In Dockerfile vs Runtime

  • ENV in the Dockerfile = build-time defaults baked into the image.
  • -e / --env-file at runtime = override per environment (dev/staging/prod).
⚠️ Don't bake secrets into the image with ENV — they're visible in docker history and to anyone who pulls the image. Inject secrets at runtime (env-file, Docker/K8s secrets) — see Secrets Management.

Docker Network Drivers

🐳 Docker · Networking · 5 min read

A network driver is the plugin that implements a network type. Docker ships several built-in drivers and supports third-party plugins for advanced setups.

DriverScopeUse case
bridgeSingle hostDefault app networking
hostSingle hostMax performance, no isolation
noneSingle hostFully disable networking
overlayMulti-hostSwarm services across nodes
macvlanSingle hostContainer appears as a physical device on the LAN
ipvlanSingle hostLike macvlan, shares MAC; L2/L3 modes
docker network create -d bridge mynet docker network create -d overlay --attachable swarmnet
💡 Choosing a driver: bridge for most single-host apps, overlay for clusters, macvlan/ipvlan when a container must appear as a real host on the physical network (e.g. legacy/network appliances).

Docker Compose

🐳 Docker · Compose & Multi-Container · 7 min read

Docker Compose defines and runs multi-container applications with a single YAML file. Instead of many docker run commands, you describe all services, networks, and volumes declaratively and bring them up together.

Example: Spring Boot + Postgres

# compose.yaml services: app: build: . ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/app SPRING_DATASOURCE_USERNAME: app SPRING_DATASOURCE_PASSWORD: secret depends_on: db: condition: service_healthy db: image: postgres:16 environment: POSTGRES_DB: app POSTGRES_USER: app POSTGRES_PASSWORD: secret volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U app"] interval: 5s retries: 5 volumes: pgdata:

Commands

docker compose up -d # build + start all services docker compose ps # status docker compose logs -f app # follow a service's logs docker compose down # stop + remove (add -v to drop volumes)
💡 Compose auto-creates a network so services reach each other by service name (db, app). depends_on + condition: service_healthy waits for the DB to be ready before the app starts.

Multi-Container Applications

🐳 Docker · Compose & Multi-Container · 6 min read

Real applications are rarely one container. A typical stack separates concerns into collaborating containers, each independently scalable and replaceable.

A Typical Stack — Visual

Browser nginxreverse proxy appSpring Boot postgres redis All on one Compose network — each service reachable by name

Design Principles

  • One concern per container — web, app, db, cache, queue separately.
  • Communicate over the network by service name, not localhost.
  • Stateful services use volumes; stateless app containers can be replaced freely.
  • Scale independentlydocker compose up --scale app=3.
💡 Compose is ideal for local development and small single-host deployments. For multi-host production at scale, the same architecture moves to Kubernetes or Swarm.

Docker Compose vs Kubernetes

🐳 Docker · Compose & Multi-Container · 6 min read

Both run multi-container apps, but at different scales. Compose orchestrates containers on one host for simplicity; Kubernetes orchestrates containers across a cluster of hosts with self-healing, scaling, and rolling updates.

Comparison

Docker ComposeKubernetes
ScopeSingle hostMulti-host cluster
ComplexityLow — one YAMLHigh — many resource types
Auto-scalingNoYes (HPA)
Self-healingLimited (restart)Yes (reschedules pods)
Rolling updatesBasicBuilt-in, advanced
Best forDev, demos, small appsProduction at scale
💡 Common path: use Compose for development and small deployments, then graduate to Kubernetes for production when you need multi-node scale, self-healing, and zero-downtime deploys. Tools like Kompose can convert a compose file into K8s manifests as a starting point.

Docker Security

🐳 Docker · Security · 7 min read

Containers share the host kernel, so a misconfigured container is a real risk. Security is layered — image, runtime, and host all matter.

Key Practices

  • Run as non-root — use a dedicated USER; never run app processes as root.
  • Minimal base images — fewer packages = smaller attack surface (distroless/alpine).
  • Drop capabilities--cap-drop ALL then add only what's needed.
  • Read-only filesystem--read-only + tmpfs for writable paths.
  • No new privileges--security-opt no-new-privileges.
  • Scan images for vulnerabilities (see Image Scanning).
  • Manage secrets outside images (see Secrets Management).
  • Keep Docker & base images patched.

Hardened Run Example

docker run -d \ --user 1001 \ --read-only --tmpfs /tmp \ --cap-drop ALL \ --security-opt no-new-privileges \ --memory 512m --cpus 1 \ myapp:1.0
⚠️ A container is not a strong security boundary like a VM. Combine container hardening with host security (rootless Docker, SELinux/AppArmor) and never run untrusted images.

Image Scanning

🐳 Docker · Security · 5 min read

Image scanning detects known vulnerabilities (CVEs) in the OS packages and libraries baked into your image. Run it in CI so vulnerable images never reach production.

Tools

ToolNotes
docker scoutBuilt into Docker — CVE analysis & recommendations
TrivyPopular open-source scanner
GrypeAnchore's scanner
SnykCommercial, dev-friendly
Registry-nativeECR/GHCR/Harbor scan on push

Usage

docker scout cves myapp:1.0 # Docker's built-in scanner trivy image myapp:1.0 # Trivy
💡 Most vulnerabilities come from the base image, not your code. Scanning + using minimal, regularly-updated base images eliminates the majority of CVEs. Fail the CI build on HIGH/CRITICAL findings.

Secrets Management

🐳 Docker · Security · 6 min read

Secrets (passwords, API keys, certs) must never be baked into images or committed to Git. Docker provides mechanisms to inject them at runtime, kept out of the image layers and docker history.

Options (worst → best)

MethodVerdict
ENV in Dockerfile❌ Baked into image, visible in history
-e at runtime⚠️ OK-ish, visible in inspect/process list
Docker/Swarm secrets✓ Mounted as files in /run/secrets, encrypted at rest
External vault (Vault, AWS Secrets Manager)✓✓ Best — rotation, audit, central control

Docker Swarm Secrets

echo "s3cr3t" | docker secret create db_password - docker service create --name app --secret db_password myapp # app reads it from /run/secrets/db_password

BuildKit Build Secrets

# secret available only during build, never in a layer RUN --mount=type=secret,id=mvncreds mvn deploy
⚠️ -e DB_PASSWORD=... leaks into docker inspect and the host process list. For anything sensitive, use file-based secrets or an external secret manager and read at startup.

Resource Limits

🐳 Docker · Security · 5 min read

By default a container can consume all host CPU and memory. Limits (enforced via Linux cgroups) prevent one container from starving others — essential for stability and a "noisy neighbour" defense.

Setting Limits

docker run -d \ --memory 512m \ # hard memory cap --memory-reservation 256m \ # soft target --cpus 1.5 \ # 1.5 CPU cores --pids-limit 200 \ # max processes myapp

In Compose

services: app: image: myapp deploy: resources: limits: { cpus: "1.5", memory: 512M } reservations: { cpus: "0.5", memory: 256M }
⚠️ For the JVM, also tell Java about the limit. Modern JDKs are container-aware (-XX:MaxRAMPercentage=75 respects the cgroup memory limit) — otherwise the JVM may size the heap to the host RAM and get OOM-killed (exit 137).

Container Orchestration

🐳 Docker · Orchestration · 6 min read

Running a few containers by hand is easy; running hundreds across many machines reliably is not. Orchestration automates deployment, scaling, networking, health, and recovery of containers across a cluster.

What Orchestrators Do

  • Scheduling — place containers on suitable nodes.
  • Scaling — add/remove replicas on demand.
  • Self-healing — restart/reschedule failed containers.
  • Service discovery & load balancing — route traffic to healthy instances.
  • Rolling updates & rollbacks — deploy without downtime.
  • Config & secret management.

The Main Options

ToolNotes
KubernetesIndustry standard, powerful, complex
Docker SwarmSimple, built into Docker
Managed (EKS/GKE/AKS)Kubernetes run by a cloud provider
NomadHashiCorp's lightweight scheduler
💡 Orchestration is where containers become production infrastructure. Docker builds and packages the unit; the orchestrator runs it reliably at scale.

Docker Swarm

🐳 Docker · Orchestration · 6 min read

Docker Swarm is Docker's native, built-in orchestrator. It turns a group of Docker hosts into a single virtual cluster managed with familiar Docker commands — far simpler than Kubernetes, if less powerful.

Concepts

  • Node — a Docker host in the swarm (manager or worker).
  • Service — the definition of a containerized task (image + replicas).
  • Task — a single running container of a service.
  • Stack — a multi-service app defined in a compose file.

Usage

docker swarm init # create the swarm (this node = manager) docker swarm join --token <token> <ip>:2377 # workers join docker service create --name web --replicas 3 -p 80:80 nginx docker service scale web=5 # scale out docker service update --image nginx:1.27 web # rolling update docker stack deploy -c compose.yaml myapp # deploy a whole stack
💡 Swarm gives you ~80% of orchestration value (replicas, rolling updates, overlay networking, secrets) with ~20% of Kubernetes' complexity. Great for small clusters; most large orgs still standardize on Kubernetes.

Docker & Kubernetes

🐳 Docker · Orchestration · 6 min read

Kubernetes (K8s) is the dominant container orchestrator. A key clarification: Docker builds images; Kubernetes runs them. They're complementary — you Dockerize your app, then deploy that image to K8s.

How They Relate

  • You build images with Docker (or any OCI builder) and push to a registry.
  • Kubernetes pulls those images and runs them in Pods.
  • K8s dropped the Docker runtime (dockershim) in 1.24, now using containerd/CRI-O — but Docker-built images still run fine (they're OCI-standard).

Core K8s Objects

ObjectRole
PodSmallest unit — one or more containers
DeploymentManages replica Pods + rolling updates
ServiceStable network endpoint + load balancing
ConfigMap / SecretConfig & secrets injection
IngressHTTP routing into the cluster
# the same Docker image, deployed to K8s kubectl create deployment myapp --image=yourrepo/myapp:1.0 kubectl scale deployment myapp --replicas=3 kubectl expose deployment myapp --port=8080
💡 "Docker vs Kubernetes" is a false choice. Docker (or Buildah/Podman) produces the image; Kubernetes orchestrates it. Learning Docker first is the right foundation for Kubernetes.

Dockerizing Spring Boot Applications

🐳 Docker · Production & Spring Boot · 9 min read

Spring Boot produces a self-contained fat jar, which makes it a natural fit for Docker. There are three common approaches — from simplest to most optimized.

Approach 1 — Simple Dockerfile

FROM eclipse-temurin:21-jre WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"]

Build & run:

./mvnw clean package -DskipTests docker build -t myapp:1.0 . docker run -d -p 8080:8080 myapp:1.0

Approach 2 — Multi-Stage (build inside Docker)

FROM maven:3.9-eclipse-temurin-21 AS build WORKDIR /src COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=build /src/target/*.jar app.jar RUN useradd -r -u 1001 spring USER spring EXPOSE 8080 HEALTHCHECK --start-period=40s CMD curl -f http://localhost:8080/actuator/health || exit 1 ENTRYPOINT ["java","-jar","app.jar"]

Approach 3 — Buildpacks (no Dockerfile)

# Spring Boot's built-in OCI image builder — layered & optimized automatically ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=myapp:1.0

Container-Friendly Config

# application.yml server: port: 8080 shutdown: graceful # drain on SIGTERM management: endpoint: health: probes: enabled: true endpoints: web: exposure: include: health,info,prometheus
# pass config via env at runtime — overrides application.yml docker run -d -p 8080:8080 \ -e SPRING_PROFILES_ACTIVE=prod \ -e SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/app \ -e JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75" \ myapp:1.0
💡 Use Spring Boot layered jars (default since 2.3) or Buildpacks so dependencies, resources, and your code are separate Docker layers — rebuilding after a code change reuses the (large, unchanged) dependency layer. Set -XX:MaxRAMPercentage so the JVM heap respects the container memory limit.

CI/CD with Docker

🐳 Docker · Production & Spring Boot · 7 min read

Docker makes CI/CD pipelines reproducible: build an image once, scan and test it, then promote that same image through environments to production.

Pipeline Flow — Visual

git push build image test +scan push registry deploy prod (K8s)

GitHub Actions Example

name: ci on: { push: { branches: [main] } } jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
💡 Tag images with the immutable git SHA (not just latest) so every deploy is traceable and rollbacks are exact. Build once, promote the same artifact — never rebuild per environment.

Container Monitoring

🐳 Docker · Production & Spring Boot · 6 min read

Production containers need continuous visibility into resource usage, health, and application metrics — well beyond ad-hoc docker stats.

The Monitoring Stack

LayerTool
Container metricscAdvisor → Prometheus
Host metricsnode-exporter
App metricsMicrometer / Actuator → Prometheus
DashboardsGrafana
LogsLoki / ELK / Fluentd
AlertsAlertmanager

Spring Boot → Prometheus

# pom: micrometer-registry-prometheus + actuator management.endpoints.web.exposure.include=health,prometheus # scrape endpoint: /actuator/prometheus
# quick container metrics via cAdvisor docker run -d -p 8081:8080 --volume=/var/run/docker.sock:/var/run/docker.sock:ro \ --volume=/:/rootfs:ro gcr.io/cadvisor/cadvisor
💡 The de-facto standard: Prometheus + Grafana for metrics, Loki/ELK for logs. Spring Boot's Actuator + Micrometer expose app metrics in Prometheus format out of the box.

Container Troubleshooting

🐳 Docker · Production & Spring Boot · 7 min read

A systematic toolkit for the common "my container won't work" situations.

Diagnostic Commands

docker ps -a # is it running? what's the exit code? docker logs --tail 100 <c> # what did it print before dying? docker inspect <c> # config, state, mounts, network docker exec -it <c> sh # poke around inside docker stats <c> # CPU/mem — OOM? docker events # real-time daemon events

Common Issues & Fixes

SymptomLikely cause
Exits immediatelyMain process ended / crashed → check logs
Exit code 137OOM-killed → raise --memory or fix a leak / JVM heap
Exit code 143SIGTERM (normal stop)
Port not reachableMissing -p, app bound to 127.0.0.1, wrong port
Can't reach other containerNot on the same user-defined network
Permission denied on volumeUID mismatch between container user & host files
Image too big / slow buildNo .dockerignore / no multi-stage
💡 Debug order: docker ps -a (state + exit code) → docker logs (why) → docker inspect (config) → docker exec (inside). 90% of issues are revealed by the first two.

Docker in Cloud Environments

🐳 Docker · Production & Spring Boot · 6 min read

Every major cloud runs Docker/OCI containers. You typically push images to a managed registry, then run them on a managed compute service — from "serverless containers" to full Kubernetes.

Where to Run Containers

CloudRegistryRun options
AWSECRECS, Fargate (serverless), EKS, App Runner
GCPArtifact RegistryCloud Run (serverless), GKE
AzureACRContainer Apps, AKS, Container Instances

Typical Flow (AWS Fargate)

aws ecr get-login-password | docker login --username AWS --password-stdin <acct>.dkr.ecr.<region>.amazonaws.com docker tag myapp:1.0 <acct>.dkr.ecr.<region>.amazonaws.com/myapp:1.0 docker push <acct>.dkr.ecr.<region>.amazonaws.com/myapp:1.0 # then define an ECS task + service (Fargate) using that image
💡 The serverless container options (Fargate, Cloud Run, Container Apps) are the fastest path to production — you provide an image and they handle servers, scaling, and load balancing. For full control/scale, use managed Kubernetes (EKS/GKE/AKS).

Production Deployment with Docker

🐳 Docker · Production & Spring Boot · 7 min read

A consolidated checklist that ties the whole tutorial together — what "production-ready" actually means for containerized apps.

Production Checklist

Image

Minimal pinned base · multi-stage · non-root · scanned · pinned tags (git SHA).

Config & Secrets

Env-based config · secrets via vault/secret store · nothing sensitive in the image.

Reliability

Health checks · restart/replica policy · graceful shutdown (SIGTERM) · resource limits.

Observability

Logs to stdout (centralized) · Prometheus metrics · alerts · tracing.

Orchestration

Run on K8s/Swarm/managed service · rolling updates · autoscaling.

Pipeline

CI builds + scans + tests · promote one immutable image · automated rollback.

Zero-Downtime Deploys

  • Rolling update — replace replicas gradually behind a load balancer (default in K8s/Swarm).
  • Blue/green — stand up the new version, switch traffic, keep old for rollback.
  • Canary — route a small % to the new version, ramp up if healthy.
  • Health checks gate traffic — only ready containers receive requests.
⚠️ Don't run a single container on one host for production — no resilience. Use an orchestrator (or managed serverless containers) for self-healing, scaling, and zero-downtime deploys.
💡 Production-ready in one line: minimal non-root scanned image · externalized config & secrets · health checks + limits + graceful shutdown · centralized logs/metrics · orchestrated rolling deploys from a CI pipeline.