Skip to main content

6.5 Compose: a stack described in a file

The docker run command in lesson 6.4 was already getting long, and it only had three options. Real services need volumes, environment variables, restart policies, and often several containers that have to find each other. Nobody remembers that as a command, and a command isn't something you can put in Git.

Docker Compose is that command written down as a file. One .yaml describing what should be running, and two words to make it so.

Where data lives: volumes

First, the question lesson 6.4 ended on.

A container's filesystem dies with it. To keep anything, you map a directory on the host into the container, so writes land on UBNT01's disk instead of inside the disposable box. That mapping is a volume, and it's the line most likely to matter when you're reading somebody else's compose file: it tells you where the real data is.

The rule of thumb: containers are disposable, volumes are not. Back up the volumes, and you can throw the containers away as often as you like. Get that backwards and you will eventually delete something you wanted.

A place for stacks

Give yourself a predictable layout, for the same reason lesson 3.3 gave every VM its own folder:

# One directory per stack, all in one place.
mkdir -p ~/docker/whoami
cd ~/docker/whoami

Write one

nano compose.yaml
services:
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
ports:
- "127.0.0.1:8081:80"

Read it as four answers. image: what to run. container_name: what to call it, instead of a random name. restart: unless-stopped: bring it back after a reboot or a crash, unless I stopped it deliberately, which is what you want for anything that matters. ports: publish it, and note the 127.0.0.1: prefix, which is doing real work.

That prefix means the service listens only on the server itself, not on the network. Compare it to lesson 6.4, where -p 8080:80 exposed nginx to your whole LAN. From here on, services in this lab are published to loopback and reached through a reverse proxy, which lesson 6.7 builds. One front door, not a scattering of open ports, is how you keep track of what a machine actually exposes.

Run it

# Read the file, pull what's missing, start everything. -d detaches.
docker compose up -d

# What did that create?
docker compose ps

# Prove it's listening on loopback only.
curl http://127.0.0.1:8081

That last command prints the container's view of the request. Now try it from your own computer at http://10.10.10.20:8081, and watch it fail: the port is bound to loopback, so nothing outside the server can reach it. The failure is the lesson.

The other verbs, which are all you need:

docker compose logs -f # follow the logs, Ctrl+C to stop
docker compose restart # bounce it
docker compose pull # fetch newer images
docker compose down # stop and remove the containers

down removes containers but leaves volumes alone, which is exactly the disposable-containers, durable-data split from earlier in this lesson.

Why this file is the point

You now have a text file that fully describes a running service. Put it in Git and you have the service's history: what changed, when, and why. Copy it to another machine and you get the same service. Hand it to a colleague and they can read what you deployed without logging into anything.

That's infrastructure as code, in its smallest useful form, and it's the same idea Module 10 scales up with Ansible. Which is a good reason to start now:

# Your stacks belong in version control from the first one.
cd ~/docker
git init
git add whoami/compose.yaml
git commit -m "whoami: first compose stack"

Leave it local for now. In lesson 6.8 it gets a remote, on a Git server you're about to build.

Tidy up before moving on:

cd ~/docker/whoami && docker compose down