The Desi Architect
Notes / Docker Sandboxes / Docker Compose Basics: Two Containers, One File
Docker Sandboxes · Tier 0: Start Here · Lab 01

Docker Compose Basics: Two Containers, One File

One file, one command, two containers that find each other by name. Read a docker-compose.yml line by line, break a service name on purpose, and learn exactly what down and down -v delete.

Time30 minutes
StackWeb on port 18111Ports 18111
MemoryAbout 40 MB
Before thisLab 00Docker Basics
Free previewVerified runWindows, Mac, LinuxBash and PowerShell
  1. 1PrepareWhat you will learn
  2. 2PredictGuess before you run
  3. 3RunStep by step
  4. 4ObserveWhat you should see
  5. 5Break itChange one thing
  6. 6Clean upLeave nothing running
On this page
  1. 00 / What You Will Learn
  2. 01 / Why It Matters
  3. 02 / What We Are Building
  4. 03 / Words You Need
  5. 04 / Predict First
  6. 05 / Step by Step
  7. 06 / What You Should See
  8. 07 / Look Inside
  9. 08 / Break It, Fix It
  10. 09 / In Real Projects
  11. 10 / Interview Corner
  12. 11 / Clean Up

00 /What You Will Learn

By the end you will be able to
  • Say what Docker Compose is, and why one file beats many docker run commands.
  • Read a docker-compose.yml from 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 cache and no IP address.
  • Know exactly what docker compose down keeps and what down -v deletes.

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 version

You 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.

In plain words

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.

About YAML

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.

Docker Compose Basics topology Your browser curl :18111 web python app cache:6379 cache redis:7.4.11-alpine3.21 redis_data volume Compose network: services find each other by name
  • web: our small Python app. It counts visits by asking Redis.
  • cache: Redis, a fast store that holds the count. Its service name is cache.
  • redis_data: a named volume so Redis keeps its data across down and up.
  • Compose also creates a private network. Inside it, every service name works like a web address.

03 /Words You Need

WordPlain meaning
ComposeThe tool that reads docker-compose.yml and runs everything in it.
ServiceOne kind of container in the file, like web or cache. A service can run as many copies.
YAMLThe file format: indented lists. Spaces only, no tabs.
NetworkA private road that Compose builds between your services. Nobody outside can use it.
Service nameThe name under services:. Inside the network it also works as the host name, so cache reaches the Redis container.
Named volumeStorage managed by Docker that outlives containers. Listed under volumes:.
Environment variableA setting passed into a container, like REDIS_HOST=cache.
depends_onStart this service after another one. It waits for the start, not for "ready" (the next lab fixes that).
CacheA fast place to keep answers you may need again. In this lab Redis plays that role, and its service is named cache.
DNSThe 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 -vdown 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.

Which terminal are you using?
mkdir docker-compose-basics && cd docker-compose-basics
unzip ~/Downloads/docker-compose-basics.zip
mkdir 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 ps
docker compose up -d
docker compose ps

up 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:18111
curl.exe http://127.0.0.1:18111
curl.exe http://127.0.0.1:18111

You 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 visits

The 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:18111

You 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 -d

Compose 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:18111

You should see: the count continues (4). Now the dangerous one:

docker compose down -v
docker compose up -d
curl http://127.0.0.1:18111

You 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.

Terminal · test-compose-basics
== Step 1: start both containers with one command cache running web running [OK] web and cache are running == Step 2: the app talks to Redis by the name 'cache' [OK] counter went 1, 2 through Redis == Step 3: logs and exec [OK] docker compose logs and exec work == Step 4: break it. Point the app at a Redis name that does not exist [OK] wrong name gives 503 and a clear log line == Step 5: fix it. Go back to the right name [OK] fixed: counter continued at 3 == Step 6: down keeps the volume, down -v deletes it [OK] after 'down' then 'up': counter continued at 4 [OK] after 'down -v' then 'up': counter restarted at 1 ALL STEPS PASSED
Observed on a laptop, not a production guarantee

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_on waits 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 .env file 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.21 is good. redis:latest can change on the next pull.
The tradeoff

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

  1. 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.
  2. 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.
  3. 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 down and docker compose down -v?
  • Why can a container reach another one by service name, and why not by IP address?
  • Does depends_on guarantee that the database is ready to accept connections? Why not?

11 /Clean Up

docker compose down -v

The automated script cleans up after itself. Next: Lab 02, Docker Compose Primer. Health checks, restart policies, and what happens when a container crashes.