CTCaliberTek
Course: Docker and Kubernetes: Step by Step · Lesson 3 of 9Log in to track your progress and earn a certificate when you finish the course.
DevOps

Docker Compose: Running Multi-Container Applications

Published August 11, 2026·Updated October 29, 2026·9 min read
Like SaveLog in to like, save, and track progress.

Introduction

Real applications are more than one container: a web app, a database, maybe a cache. Docker Compose describes all of them in one file and starts them together with a single command. This lesson builds a web and database setup with health-based startup ordering, persistent storage, and safe handling of secrets.

Prerequisites

  • Docker installed with the Compose plugin
  • A Dockerfile for a web application

Step-by-Step Guide

Step 1: Create a compose.yaml

The preferred file name is compose.yaml. The old top-level version key is obsolete and should be left out, since Compose always uses the latest schema. This file defines a web service built from your Dockerfile and a PostgreSQL database with a named volume and a health check.

services:
  web:
    build: .
    ports:
      - "8000:3000"
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: app
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  db-data:

Step 2: Keep secrets out of the file

Compose substitutes variables from your shell or from a .env file next to compose.yaml. Put the password in .env, and add .env to .gitignore and .dockerignore so it is never committed or baked into an image. For real secrets in production, prefer Compose secrets or your platform's secret manager over environment variables.

# .env  (never commit this file)
DB_PASSWORD=change-me-locally

Step 3: Start, inspect, and stop the stack

Services on the same Compose network reach each other by service name, so the web app connects to the database at the host name db. Use down to stop and remove containers, and add -v only when you want to delete the named volumes and their data too.

docker compose up -d
docker compose ps
docker compose logs -f web
docker compose exec db psql -U app -d app

docker compose down        # keeps the db-data volume
docker compose down -v     # also deletes the data

Step 4: Understand startup order and health

depends_on with condition service_healthy makes the web service wait until the database passes its health check, which avoids connection errors during startup. A plain depends_on only waits for the container to start, not to be ready. Check the resolved configuration with docker compose config.

docker compose config

Step 5: Use profiles for optional services

A service with a profiles list only starts when that profile is enabled. This is useful for tools you do not always want, such as a database admin UI. Services without profiles always start.

services:
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles: [debug]

# docker compose --profile debug up -d

FAQs

What is the difference between docker-compose and docker compose?
docker-compose is the older standalone tool. docker compose is the current plugin built into the Docker CLI and is what the docs use. Prefer the plugin.
Is Compose suitable for production?
It is great for development, testing, and small single-host deployments. For multi-node scaling and self-healing across many machines, teams usually move to an orchestrator such as Kubernetes.

Summary

Describe services, volumes, and health checks in compose.yaml, keep passwords in an ignored .env file, rely on service names for networking, and use health-based depends_on and profiles for reliable startup and optional tools.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.