00 /What You Will Learn
- Say what Docker Compose is, and why one file beats many
docker runcommands. - Read a
docker-compose.ymlfrom top to bottom without guessing. - Start a web app and Redis with one command, and stop them with one command.
- Explain why the web app can reach Redis using the word
cacheand no IP address. - Know exactly what
docker compose downkeeps and whatdown -vdeletes.
Before you start
Finish Lab 00 first, or be comfortable with these words: image, container, port, volume. Then check that Compose is installed:
docker compose versionYou should see Docker Compose version v2 or newer. Docker Desktop includes it. If you see "docker: compose is not a docker command", update Docker Desktop. Old tutorials use docker-compose with a hyphen. This lab uses the modern docker compose with a space.
01 /Why It Matters
In Lab 00 you typed one long docker run command for one container. A real app needs more: a web server, a database, a cache. Typing three long commands in the right order every morning is painful. Someone forgets a flag. A teammate uses a different port. Two weeks later nobody remembers how the setup was supposed to work.
Think of a wedding caterer. You do not phone the cook, then the sweet maker, then the tent wala, one by one, each time. You hand over one order sheet with every item, quantity and timing. Docker Compose is that order sheet for containers. You write what you want in one file. One command starts everything. One command stops it.
Docker Compose reads one file that lists your containers and how they connect, then starts them all together. The file also becomes the written record of how your app runs.
The file is written in YAML. It looks like an indented list. Two rules: use spaces (never tabs), and indentation means "this belongs to the line above". If Compose says it cannot parse your file, a wrong indent is almost always the reason.
02 /What We Are Building
Two containers on a private network that Compose creates for you. Only the web port is open to your laptop. Redis is reachable from the web container, and from nobody else.
web: our small Python app. It counts visits by asking Redis.cache: Redis, a fast store that holds the count. Its service name iscache.redis_data: a named volume so Redis keeps its data acrossdownandup.- Compose also creates a private network. Inside it, every service name works like a web address.
03 /Words You Need
| Word | Plain meaning |
|---|---|
| Compose | The tool that reads docker-compose.yml and runs everything in it. |
| Service | One kind of container in the file, like web or cache. A service can run as many copies. |
| YAML | The file format: indented lists. Spaces only, no tabs. |
| Network | A private road that Compose builds between your services. Nobody outside can use it. |
| Service name | The name under services:. Inside the network it also works as the host name, so cache reaches the Redis container. |
| Named volume | Storage managed by Docker that outlives containers. Listed under volumes:. |
| Environment variable | A setting passed into a container, like REDIS_HOST=cache. |
| depends_on | Start this service after another one. It waits for the start, not for "ready" (the next lab fixes that). |
| Cache | A fast place to keep answers you may need again. In this lab Redis plays that role, and its service is named cache. |
| DNS | The phone book that turns a name into an address. Compose runs a small one inside your network, which is why a service name works as a host name. |
| down, down -v | down removes containers and the network. down -v also removes volumes, which means your data. |
04 /Predict Before You Run
Question 1: The web app connects to the host name "cache". There is no entry for it on your laptop. How does it find Redis?
Docker runs a small DNS service for the Compose network. It knows that the name cache belongs to the Redis container and answers with that container's current address. This is why you never type container IP addresses: they change every time a container is recreated.
Question 2: You run docker compose down and then docker compose up -d. Does the visit counter keep its value?
Yes. down removes the containers and the network but leaves named volumes alone. Redis writes to the redis_data volume (with --appendonly yes), so the new container reads the old data.
Question 3: You run docker compose down -v and then up -d. Now what?
The counter starts again at 1. The -v means "also remove volumes". It is the one flag that deletes your data. Treat it the way you treat rm -rf.
05 /Step by Step
Step 0: Get the lab
Download the zip with the button at the top of this page. Unzip it into a new folder and open a terminal inside that folder. Every command below runs from there.
mkdir docker-compose-basics && cd docker-compose-basics
unzip ~/Downloads/docker-compose-basics.zipmkdir docker-compose-basics; cd docker-compose-basics
Expand-Archive -Path "$HOME/Downloads/docker-compose-basics.zip" -DestinationPath .Unzip the lab into a new folder and open a terminal in it. To run everything with checks at once, use ./test-compose-basics.sh (Mac, Linux, Git Bash) or .\test-compose-basics.ps1 (PowerShell). To learn, follow the steps by hand.
First, read the file
This is the whole lab. Every line has a comment. Read it top to bottom before you run anything.
# Read this file top to bottom. It is the whole lab.
# "services" is the list of containers we want. Compose starts them together.
services:
# Service 1: our small web app.
web:
image: python:3.11.17-alpine3.24 # ready-made image with Python inside
working_dir: /app # folder where the command below runs
command: python /app/server.py # what to run when the container starts
volumes:
- ./app/server.py:/app/server.py:ro # share our file into the container, read only
ports:
- "127.0.0.1:18111:8080" # your-laptop-port : container-port
environment:
- REDIS_HOST=${REDIS_HOST:-cache} # where to find Redis. Default is the word "cache"
depends_on:
- cache # start Redis first
# Service 2: Redis, the counter store. The service name "cache" is also its
# network name. Compose gives every service a DNS name equal to its name.
cache:
image: redis:7.4.11-alpine3.21
command: redis-server --appendonly yes # save every write to disk
volumes:
- redis_data:/data # named volume: survives "down", dies with "down -v"
# Named volumes are managed by Docker, not by a folder on your laptop.
volumes:
redis_data:The file in four sentences
services:
The list of containers. We have two: web and cache.
web
Uses a ready-made Python image, shares one file (server.py) into it, opens port 18111 on your laptop and passes REDIS_HOST so the app knows where Redis is.
cache
Uses the official Redis image, saves writes to disk (--appendonly yes) in the redis_data volume.
volumes:
Declares the named volume so Docker creates and keeps it.
Step 1: Start everything
docker compose up -d
docker compose psdocker compose up -d
docker compose psup creates the network, the volume and both containers. -d runs them in the background. You should see: web and cache both running.
Step 2: Use it
curl http://127.0.0.1:18111
curl http://127.0.0.1:18111curl.exe http://127.0.0.1:18111
curl.exe http://127.0.0.1:18111You should see: "visits": 1, then "visits": 2. The web container asked Redis, and Redis counted.
Step 3: Look inside
docker compose logs web
docker compose exec cache redis-cli GET visitsThe first command shows what the app printed, including redis at cache:6379. The second asks Redis directly and should print 2.
Step 4: Break it. Point the app at a Redis name that does not exist
REDIS_HOST=wrongname docker compose up -d
curl -i http://127.0.0.1:18111$env:REDIS_HOST = "wrongname"; docker compose up -d; Remove-Item Env:REDIS_HOST
curl.exe -i http://127.0.0.1:18111You should see: HTTP status 503 and a message cannot reach redis at wrongname:6379. docker compose logs web prints the same sentence. The app was fine. The name was wrong. Most "Docker is broken" problems turn out to be exactly this.
Step 5: Fix it
docker compose up -dCompose recreates only the container that changed. You should see: the counter continues from 3, because Redis never stopped.
Step 6: down keeps the data, down -v deletes it
docker compose down
docker compose up -d
curl http://127.0.0.1:18111You should see: the count continues (4). Now the dangerous one:
docker compose down -v
docker compose up -d
curl http://127.0.0.1:18111You should see: the count restarts at 1. The -v deleted the volume and the data in it.
06 /What You Should See
This is the real output of the automated run (test-compose-basics). Every [OK] line is a check that passed.
The numbers above come from one run on one laptop with Docker Desktop. Your machine will show different timings and counters. What must match is the shape of the result, the [OK] lines.
07 /Look Inside
1. See the merged configuration
Run: docker compose config
You should see: your file with every default filled in, plus the full names Compose gives things (docker-compose-basics_default for the network).
It means: Compose builds names from the folder name. That is why two projects in two folders do not collide.
2. See the network
Run: docker network ls and docker network inspect docker-compose-basics_default
You should see: both containers listed with a private IP address each.
It means: the name cache is resolved to that private IP. Remove and recreate the container, and the IP may change while the name keeps working.
3. See the data in Redis
Run: docker compose exec cache redis-cli KEYS "*"
You should see: visits. That one key is the counter.
08 /Break It, Then Fix It
Predict: if you rename the service cache to store in the compose file but leave REDIS_HOST alone, what breaks and where will you see it first?
Try it. Change the service name, run docker compose up -d, and visit the app. What happened: a 503, because nothing is called cache any more. The name in REDIS_HOST and the service name must match. Change REDIS_HOST to store and it works again.
Stretch: change the port line to 127.0.0.1:18112:8080. Which URL works now, and did you have to touch the app? (No. The right-hand number is the app's own port. Only the left-hand number is yours to choose.)
09 /What Changes in Real Projects
- Compose runs on one machine. It is perfect for development, tests and small deployments. For many machines, teams use Kubernetes or a managed service.
depends_onwaits for the container to start, not for the app inside to be ready. Redis may still be loading when the web app starts. The next lab, Docker Compose Primer, adds health checks for this.- Secrets do not belong in the file. Passwords go in a separate
.envfile that you do not commit, or in a secrets manager. - Bind ports to
127.0.0.1(as this lab does) unless you really want the service reachable from other computers. - Pin image versions.
redis:7.4.11-alpine3.21is good.redis:latestcan change on the next pull.
You gained one file that describes the whole setup and one command to start it, so every teammate runs the same thing. You gave up some visibility: Compose hides details, so when something fails you must read its output to find which container and why. It is also built for one machine, not for a fleet.
10 /Interview Corner
- What problem does Docker Compose solve? Running several containers that belong together. One file describes them and their connections, and one command starts or stops all of them. It also documents the setup.
- How do containers in one Compose project find each other? Compose creates a private network with built-in DNS. Each service name is a host name on that network.
- Where should a database keep its data when it runs in a container? In a volume, not in the container's own file system. Containers are disposable. Volumes are not.
Follow-up questions the interviewer will ask
- What is the difference between
docker compose downanddocker compose down -v? - Why can a container reach another one by service name, and why not by IP address?
- Does
depends_onguarantee that the database is ready to accept connections? Why not?
11 /Clean Up
docker compose down -vThe automated script cleans up after itself. Next: Lab 02, Docker Compose Primer. Health checks, restart policies, and what happens when a container crashes.