CertGrid CertGrid
Hands-on Lab·Docker

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

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.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. 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 under services is one container. There is no version key 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 config prints it back fully resolved if you want to check before starting anything.

  2. Bring it up

    up -d creates 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 Started

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

  3. See what is running

    docker compose ps is scoped to this project only - unlike docker 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/tcp

    Expected resultBoth services Up, and only web shows a host mapping.

    Verify it worked

    bash
    curl -s -o /dev/null -w "%{http_code}" http://localhost:8099; echo200

    Success conditionYou get 200 from the published port. Note cache lists 6379/tcp with no host mapping - it is reachable from web, not from you.

  4. Read the logs

    docker compose logs interleaves every service and prefixes each line with the service that produced it. Add a service name to narrow it, or -f to 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.

  5. Run a command inside a service

    docker compose exec targets 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.3

    Expected resultA response from each service.

    Success conditionPONG proves the cache is not merely running but answering. Up is not the same as ready - a distinction guide 26 is built around.

  6. 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/start different from down/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 ago

    Expected resultweb exited while cache stays up, then web comes back.

    Verify it worked

    bash
    docker compose start web Container cg-first-web-1 Starting Container cg-first-web-1 Started

    Success conditionOnly the named service changed state. Note ps needed -a to show the stopped one.

  7. Tear it down

    down is the counterpart to up: 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 Removed

    Expected resultEvery container removed, then the project network.

    Success conditiondocker compose ps -a is empty and the project network is gone.

Troubleshooting

Official sources