Your application is no longer one process. There is the API, there is MongoDB, there is Redis - and to run the whole thing locally you must call
docker run three times with a long list of flags, in the right order, remembering the network and the fact that the database needs a moment to start. Every new team member receives these instructions by word of mouth, and slightly differently each time.A legion did not march on verbal arrangements. It had a daily order: a written list of units, their sequence, and what each takes along. Docker Compose is such an order - it manages several containers at once.
Do not confuse it with its neighbours: building images from source is what
docker build and a Dockerfile do, testing is your pipeline's job, and watching resource usage belongs to the tools you met under observability.The whole configuration fits in one
docker-compose.yml file:1services:
2 mongodb:
3 image: mongo:7
4 volumes:
5 - mongo-data:/data/db
6 healthcheck:
7 test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
8 interval: 10s
9 retries: 5
10
11 redis:
12 image: redis:7-alpine
13
14 api:
15 build: .
16 ports:
17 - "3000:3000"
18 environment:
19 DATABASE_URL: mongodb://mongodb:27017/legions
20 REDIS_HOST: redis
21 depends_on:
22 mongodb:
23 condition: service_healthy
24 redis:
25 condition: service_started
26
27volumes:
28 mongo-data:Each entry under
services is one container. image takes a ready image, build: . tells Compose to build one from the Dockerfile in this directory.The
field provides the services' startup order. So our three services come up like this: MongoDB first, then Redis, and last the API, which waits for both.depends_on
But here lies the trap most first configurations hit:
depends_on alone waits only for the container to start, not for it to be ready. MongoDB needs a few seconds to load its data - the container is already running while the database still refuses connections. The API starts, tries to connect and dies.That is why the database carries a
healthcheck and depends_on carries the condition condition: service_healthy. Now Compose waits until the command in healthcheck starts succeeding. Redis comes up quickly and service_started suffices for it - the default behaviour, meaning "the container is running".Remember this difference, @name: started is not the same as ready. The great majority of "connection refused" errors at environment startup come from exactly here.
Note the database address:
mongodb://mongodb:27017/legions. There is no localhost there and no IP address - there is a service name.Compose creates a shared network for all the services and registers each in it under its own name. The
api container connects to the database by simply writing mongodb - the rest happens by itself. You need no IP addresses and no configuration.Note too what is absent from our file: MongoDB and Redis have no
ports section. That is deliberate. Without it a service is visible only inside the Compose network - the API can reach it, nobody outside can. We expose a port only where it is genuinely needed, which is the API.A container is impermanent by design: delete it and everything it wrote disappears. For a database that is a disaster, so volumes store persistent data outside the container.
The
mongo-data:/data/db entry says: whatever MongoDB writes into the /data/db directory, keep it in a volume named mongo-data, living independently of the container. Deleting and recreating the container will not touch that data.Volumes do not limit processor usage, do not encrypt communication and scale nothing - they store data, and that is all.
The whole order is carried out by one command:
1docker compose up --build -dThe two parts after
up are worth separating. --build rebuilds the images before starting - without it Compose uses the previously built one, however much you changed the code. -d (for detached) sends everything to the background and gives you the terminal back.Stopping has two variants, and the difference between them can be costly.
docker compose down removes the containers and the network, leaving the volumes. docker compose down -v removes the volumes too - meaning the database's data. You run the first daily; the second only when you truly want to start from an empty database.The daily order is written and the units march in sequence:
docker-compose.yml, and every entry under services is one container,depends_on provides the services' startup order,depends_on waits for the container to start, not for the service to be usable,healthcheck plus condition: service_healthy waits for actual readiness; service_started is enough for fast services,mongodb://mongodb:27017, with no IP addresses and no network configuration,ports section means the service is visible only inside the Compose network,docker compose up --build -d, where --build rebuilds images and -d sends it to the background,down leaves the volumes, down -v removes them along with the data.In the next lesson we will deal with secrets - the passwords and keys we still wrote out in plain sight in this file. For now remember: Compose is a daily order - one file replacing the verbal instructions everybody remembered differently.