Lesson 3 · Containers & Docker

Running containers

Ports, volumes, env vars, networks — the four dials that turn an image into a useful process.

Your win: know the four things you configure when you run a container — ports, volumes, environment, network — tell a named volume from a bind mount, and have a first move when a container won't start.

In plain English Plain English: running a container means deciding how the process sees ports, files, config, and its network neighbors.

From image to running container

An image on its own does nothing. docker run turns it into a container, and the flags you pass are how it connects to the outside world.1 There are four you'll use constantly — the same four appear as keys in Compose (Lesson 4), so learning them here pays off twice.

1 · Ports — -p host:container

A container's network is private. To reach a port from your Mac, you publish it: -p 5432:5432 maps host port 5432 → container port 5432.

Anchor local/docker-compose.infra.yaml:5-6 — the db container publishes "5432:5432". That's the only reason your local psql or a GUI on the host can reach the containerised Postgres. Remove it and the DB still runs — other containers still reach it — but your host can't.

2 · Volumes — persist data, or mount host files

A container's writable layer is thrown away when the container is removed. To keep data, or to feed files in, you mount a volume. Two kinds, and the difference is a favourite interview question:2

Named volumeBind mount
Backed bystorage Docker managesa specific host path
Syntax-v myvol:/data-v ./cfg:/configs
Use fordata that must persistcode/config you edit on the host
Survives removal?yes, until you delete the volumelives on the host regardless
Anchor — both kinds, side by side local/docker-compose.infra.yaml:11-13 — the db uses both: a named volume postgres_data:/var/lib/postgresql/data (so your data survives a restart) and a bind mount of the migrations directory from the host (read-only seed SQL). A service like fink (docker-compose.service.macos.yaml:28-31) bind-mounts its configs and secrets in — config you can edit without rebuilding the image.

3 · Environment variables — -e KEY=value

Runtime configuration handed to the process. In the repo, services get GOOGLE_APPLICATION_CREDENTIALS and Postgres gets POSTGRES_PASSWORD this way (docker-compose.infra.yaml:7-8). Env vars are the standard way to keep config out of the image — the same image runs in local/stag/prod with different env.

4 · Networks — how containers find each other

Containers on the same Docker network reach each other by service name as a hostname — Docker runs an internal DNS.3 No IP juggling.

The insight that trips people up Port publishing (-p) is only for the host to reach a container. Container-to-container traffic doesn't need it — they just use the service name on their shared network. In this repo everything sits on local-manabie-network, so a service connects to Postgres at host db:5432, never localhost.

When a container won't start — first moves

Most "it won't start" cases are one of: bad command:/args, a missing bind-mounted file, a wrong env var, or a dependency that isn't ready yet — which is exactly what Lesson 4's healthchecks solve.

Read this next

Docker — running containers & managing data

The full run surface (ports, env, restart) and the volumes-vs-bind-mounts guide.

docs.docker.com — Running containers
docs.docker.com — Manage data (volumes vs bind mounts)

Common mistake Common mistake: thinking ports, bind mounts, named volumes, and networks are separate trivia instead of one runtime surface you configure together.

Check yourself (from memory)

Q1. Publishing a port with -p 5432:5432 is needed so that…

Publishing is host→container. Container→container uses the service name on a shared network, no -p required.

Q2. For a database's data files you should use a…

Named volumes are for persistent data (the repo's postgres_data); bind mounts are for host code/config you edit.

Q3. In this repo a service connects to Postgres at…

Same network → resolve by service name. Everything sits on local-manabie-network, so it's db:5432.
Name the four run-time dials, and named-volume vs bind-mount.
recall, then click to reveal
FOUR DIALS: (1) PORTS -p host:container — publish so the HOST can reach it (not needed container↔container). (2) VOLUMES — persist/attach storage. (3) ENV -e KEY=val — runtime config (keeps the image env-agnostic). (4) NETWORK — same network ⇒ reach peers by SERVICE NAME via Docker DNS. NAMED VOLUME = Docker-managed, for persistent DATA (repo postgres_data); BIND MOUNT = a HOST PATH, for config/code you edit (repo service configs/secrets). Debug a dead container with docker ps -alogsinspectexec -it … sh.
Want the difference between ENTRYPOINT and command: args, or why localhost inside a container isn't your Mac? Ask me.

1. Docker — Running containers.

2. Docker — Manage data in Docker (volumes & bind mounts).

3. Docker — Networking overview.