Docker Compose Fundamentals
One file replaces a page of docker run flags. Write a two-service compose.yaml, bring it up, inspect and exec into it, stop and start a single service, and tear the whole thing down - with the real output of every command.
Docker Compose Guide 24 of 46 Beginner
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- Docker Compose5.4.0
- Architectureamd64
- TimeAbout 13 min
- Reviewed21 August 2026
Tested on the versions above. Container names are derived from the directory name, so yours will differ. Match the shape of the output, not the exact names.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- You can run containers and publish ports - guide 9 in this path.
- Compose v2 is included with Docker Engine; check with
docker compose version.
-
The file replaces the flags
Everything you have been passing to
docker run- image, ports, command - becomes a key in a YAML file. Each top-level entry underservicesis one container. There is noversionkey any more; it was made obsolete and current Compose warns if you include it.bash cat compose.yamlservices: web: image: nginx:alpine ports: - "8099:80" cache: image: redis:7-alpine command: redis-server --save ""Expected resultTwo services, one of which publishes a port.
Success conditionThe file parses.
docker compose configprints it back fully resolved if you want to check before starting anything. -
Bring it up
up -dcreates everything the project needs and starts it in the background. Compose also creates a network for the project and puts every service on it, which is why services can reach each other by name with no extra configuration - the DNS behaviour from guide 10, arranged for you.bash Example session docker compose up -d Container cg-first-web-1 Created Container cg-first-cache-1 Created Container cg-first-web-1 Starting Container cg-first-cache-1 Starting Container cg-first-web-1 Started Container cg-first-cache-1 StartedExpected resultCreated then Starting then Started for each service.
Success conditionBoth services report Started. Names follow
project-service-index, and the project name defaults to the directory name. -
See what is running
docker compose psis scoped to this project only - unlikedocker ps, it will not drown you in containers from elsewhere on the machine. The SERVICE column is the name from your file; that is the name you use in every other Compose command.bash Example session docker compose psNAME IMAGE SERVICE STATUS PORTScg-first-cache-1 redis:7-alpine cache Up Less than a second 6379/tcpcg-first-web-1 nginx:alpine web Up Less than a second 0.0.0.0:8099->80/tcp, [::]:8099->80/tcpExpected resultBoth services Up, and only
webshows a host mapping.Verify it worked
bash curl -s -o /dev/null -w "%{http_code}" http://localhost:8099; echo200Success conditionYou get 200 from the published port. Note
cachelists6379/tcpwith no host mapping - it is reachable fromweb, not from you. -
Read the logs
docker compose logsinterleaves every service and prefixes each line with the service that produced it. Add a service name to narrow it, or-fto follow. This is the first thing to run when a service is not behaving, and it is usually the last thing you need.bash Example session docker compose logs --tail 2cache-1 | 1:M 20 Aug 2026 07:33:34.428 * Server initializedcache-1 | 1:M 20 Aug 2026 07:33:34.428 * Ready to accept connections tcpweb-1 | 2026/08/20 07:33:34 [notice] 1#1: start worker process 31web-1 | 172.19.0.1 - - [20/Aug/2026:07:33:34 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.18.0" "-"Expected resultPrefixed lines from both services, including the request you just made.
Success conditionYou can see your own curl in nginx's access log. That closes the loop between the published port and the container.
-
Run a command inside a service
docker compose exectargets a SERVICE rather than a container name, so you never have to look up the generated name. This is how you check a service from the inside - the health of a cache, the version of a binary, the contents of a config file.bash docker compose exec cache redis-cli pingPONGdocker compose exec web nginx -vnginx version: nginx/1.31.3Expected resultA response from each service.
Success condition
PONGproves the cache is not merely running but answering. Up is not the same as ready - a distinction guide 26 is built around. -
Stop one service without touching the others
Compose commands take an optional service name, and most of them default to every service when you leave it off. Stopping a service leaves the container in place - it can be started again, keeping its filesystem - which is what makes
stop/startdifferent fromdown/up.bash Example session docker compose stop web Container cg-first-web-1 Stopping Container cg-first-web-1 Stoppeddocker compose ps -a --format "table {{.Service}}\t{{.Status}}"SERVICE STATUScache Up Less than a secondweb Exited (0) Less than a second agoExpected result
webexited whilecachestays up, thenwebcomes back.Verify it worked
bash docker compose start web Container cg-first-web-1 Starting Container cg-first-web-1 StartedSuccess conditionOnly the named service changed state. Note
psneeded-ato show the stopped one. -
Tear it down
downis the counterpart toup: it stops and removes the containers AND the network Compose created. It does not remove named volumes unless you add-v, which is a deliberate safety choice - your data survives a careless teardown.bash Example session # removes containers and the project network; add -v to also delete named volumesdocker compose down Container cg-first-cache-1 Removing Container cg-first-web-1 Removed Container cg-first-cache-1 Removed Network cg-first_default Removing Network cg-first_default RemovedExpected resultEvery container removed, then the project network.
Success condition
docker compose ps -ais empty and the project network is gone.
Troubleshooting
no configuration file provided: not found
Why: Compose looks for
compose.yaml,compose.yml,docker-compose.yamlordocker-compose.ymlin the current directory. You are somewhere else, or the file is named something else.Fix:Change into the directory holding the file, or point at it explicitly with
-f.bash docker compose -f /path/to/compose.yaml psThe version key produces a warning
Why: The top-level
version:field is obsolete in the Compose specification. It is ignored, and current versions warn about it.Fix:Delete the line. Nothing else needs to change -
services:at the top level is the modern form.bash docker compose config# prints the fully resolved file, with obsolete keys flaggedPorts are already allocated
Why: Another process - often a previous project you did not take down - already holds the host port.
Fix:Find the holder, or change the host side of the mapping. Only the left-hand number has to be free.
bash docker ps --filter publish=8099 --format "{{.Names}} {{.Ports}}"Container names collide with another project
Why: The project name defaults to the directory name, so two checkouts in identically named directories fight over the same container names.
Fix:Set the project name explicitly with
-p, or thename:top-level key in the file.bash docker compose -p myproject up -d