Docker Compose Orchestration: Multi-Container Apps
Build and orchestrate multi-container applications with Docker Compose - services, networks, volumes, and environment configuration
Greetings, brave adventurer! You have already forged single containers in the fires of the Container Coast. But real applications are rarely a lone process - they are a web server, a database, a cache, and a background worker, all needing to find each other, share secrets, and start in the right order. Coordinating them by hand with a dozen docker run flags is a path to madness.
This quest, Docker Compose Orchestration, hands you the conductor’s baton. With a single declarative file and one command, you will bring an entire ensemble of containers to life, wire them together, and tear them down just as cleanly.
📖 The Legend Behind This Quest
When developers first containerized their apps, they discovered a new problem: an app and its database are two containers, and keeping them in sync through raw docker run invocations was error-prone and impossible to share. Each teammate had a slightly different startup ritual, and the old curse - “but it starts on my machine” - threatened to return.
Docker Compose answered with a simple idea: describe the whole stack in one YAML file, commit it to the repository, and let docker compose up reproduce the exact same environment for everyone. The file becomes the single source of truth - infrastructure you can read, review, and version like code.
Master Compose and you hold the keystone of local development environments and the on-ramp to every orchestration tool that follows, from CI pipelines to Kubernetes.
🎯 Quest Objectives
By the time you complete this journey, you will have mastered:
Primary Objectives (Required for Quest Completion)
- The Compose File - Write a
compose.yamlthat defines multiple services declaratively - Services & Networking - Let containers discover and talk to each other by service name
- Volumes - Persist database data so it survives container restarts and rebuilds
- Lifecycle Commands - Bring the stack up, view logs, and tear it down with single commands
Secondary Objectives (Bonus Achievements)
- depends_on & Health - Control startup order and wait for dependencies to be healthy
- Environment & .env - Configure services with environment variables and an
.envfile - Build vs Image - Mix locally built services with pulled images in one stack
Mastery Indicators
You’ll know you’ve truly mastered this quest when you can:
- Explain how one service reaches another using only its service name
- Describe why a named volume keeps data that a container’s writable layer would lose
- Add a new service to an existing stack without breaking the others
- Move a secret out of the compose file and into an
.envfile
🗺️ Quest Prerequisites
📋 Knowledge Requirements
- Completion of Container Fundamentals (images, containers, build, run)
- Comfort building and running a single container
- Ability to edit YAML carefully (indentation matters)
🛠️ System Requirements
- Modern operating system (Windows 10+, macOS 10.14+, or Linux)
- Docker with the Compose plugin (
docker compose versionshould work) - A terminal and a text editor or IDE (VS Code recommended)
- Internet connection for pulling base images
🧠 Skill Level Indicators
This 🟡 Medium quest expects:
- You completed Container Fundamentals or can already build and run images
- You are comfortable with precise YAML indentation
- Ready for 75-90 minutes of focused, hands-on learning
🌍 Choose Your Adventure Platform
Compose ships as a plugin inside modern Docker, so once Docker itself runs, Compose runs everywhere the same way. Verify it before you begin: docker compose version.
🍎 macOS Kingdom Path
Click to expand macOS instructions
```bash # Docker Desktop already bundles the Compose plugin docker compose version # If you installed only the engine via colima, add the plugin brew install docker-compose ``` **macOS-Specific Notes:** - With Docker Desktop, no separate install is needed. - Use the modern `docker compose` (space) form, not the legacy `docker-compose` (hyphen) binary.🪟 Windows Empire Path
Click to expand Windows instructions
```powershell # Docker Desktop on Windows includes Compose docker compose version # Run commands from a WSL 2 distro for the smoothest experience wsl docker compose version ``` **Windows-Specific Notes:** - Keep your project files inside the WSL 2 filesystem for fast bind mounts. - The Compose plugin is installed with Docker Desktop automatically.🐧 Linux Territory Path
Click to expand Linux instructions
```bash # Install the Compose plugin if it isn't already present sudo apt update && sudo apt install -y docker-compose-plugin # Debian/Ubuntu # sudo dnf install -y docker-compose-plugin # Fedora/RHEL docker compose version ``` **Linux-Specific Notes:** - Native Docker on Linux gives the fastest builds and bind mounts. - If `docker compose` is missing, the `docker-compose-plugin` package provides it.☁️ Cloud Realms Path
Click to expand Cloud/Container instructions
```bash # Codespaces and most cloud dev environments include Compose docker compose version # Forward the published ports (e.g. 8080) through your platform's port forwarding ``` **Cloud-Specific Notes:** - GitHub Codespaces ships Docker and Compose preinstalled. - Remember to forward published ports so you can reach the app in your browser.🧙♂️ Chapter 1: The Compose File - One Stack, One Command
A single YAML file replaces a wall of docker run flags. Learn its shape and you can describe any stack.
⚔️ Skills You’ll Forge in This Chapter
- The anatomy of a
compose.yamlfile - Defining a service from a pulled image and from a local build
- The core lifecycle commands:
up,ps,logs,down
🏗️ Your First Compose File
A Compose file is a map of services (each becomes one or more containers), plus optional top-level networks and volumes. Here is a minimal two-service stack: a web app that talks to Redis.
app.py - a tiny Flask app that counts visits in Redis:
import os
from flask import Flask
import redis
app = Flask(__name__)
cache = redis.Redis(host=os.environ.get("REDIS_HOST", "redis"), port=6379)
@app.route("/")
def hello():
count = cache.incr("hits")
return f"Hello from Compose! This page has been viewed {count} times.\n"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Dockerfile - to build the web service:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
requirements.txt:
flask
redis
compose.yaml - the conductor’s score:
services:
web:
build: . # build the image from the local Dockerfile
ports:
- "8080:5000" # host 8080 -> container 5000
environment:
REDIS_HOST: redis # match the service name below
depends_on:
- redis
redis:
image: redis:7-alpine # pulled, not built
Bring the whole thing up:
# Build images as needed and start everything in the background
docker compose up -d
# See the services and their status
docker compose ps
# Follow the logs from all services (Ctrl-C to stop following)
docker compose logs -f
# Reload http://localhost:8080 a few times and watch the counter climb
# Stop and remove the containers, networks (keep named volumes)
docker compose down
One command started an app and a database, networked them, and published a port. That is the entire promise of Compose.
🔍 Knowledge Check: The Compose File
- Which service is built locally, and which is pulled from a registry?
- What does
"8080:5000"map, and which side is the host? - What does
docker compose downremove, and what does it keep by default?
⚡ Quick Wins and Checkpoints
- Stack is up:
docker compose psshows bothwebandredisrunning - Counter works: Reloading the page increments the visit count
- Clean teardown:
docker compose downremoved the containers cleanly
🧙♂️ Chapter 2: Networks, Service Discovery, and Volumes
How did the web service reach Redis using just the hostname redis? And why didn’t your data vanish? Two of Compose’s quiet superpowers: automatic networking and named volumes.
⚔️ Skills You’ll Forge in This Chapter
- Service discovery by name on the default Compose network
- Defining custom networks to segment traffic
- Persisting data with named volumes
🏗️ Networking and Service Discovery
Compose puts every service on a shared, private network and registers each service name as a DNS hostname. That is why redis.Redis(host="redis") just works - redis resolves to the Redis container’s IP automatically. You never hard-code IP addresses.
You can also define custom networks to isolate tiers, so that, for example, the database is only reachable by the backend and not by anything else:
services:
web:
build: .
ports:
- "8080:5000"
networks:
- frontend
- backend
depends_on:
- redis
redis:
image: redis:7-alpine
networks:
- backend # only on backend; nothing on frontend can reach it
networks:
frontend:
backend:
🏗️ Persisting Data with Volumes
A container’s writable layer is ephemeral - delete the container and its data is gone. A named volume is storage managed by Docker that lives independently of any container, so your database survives restarts, rebuilds, and down/up cycles. Compare two ways to attach storage:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: example
volumes:
- db-data:/var/lib/postgresql/data # named volume: durable, Docker-managed
- ./init:/docker-entrypoint-initdb.d # bind mount: maps a host folder in
volumes:
db-data: # declares the named volume
- A named volume (
db-data) is the right tool for database data: durable and portable. - A bind mount (
./init:...) maps a host directory straight into the container - perfect for live-reloading source code during development.
# Bring the stack up, then prove persistence:
docker compose up -d
docker compose down # containers gone...
docker compose up -d # ...but db-data volume is reattached, data intact
# List and inspect volumes
docker volume ls
docker compose down -v # the -v flag DOES delete named volumes — use with care
🔍 Knowledge Check: Networking & Volumes
- Why can the web service use
redisas a hostname without any IP configuration? - What is the difference between a named volume and a bind mount?
- Which command removes named volumes, and why is it dangerous?
🧙♂️ Chapter 3: Configuration, Startup Order, and a Full Stack
Production-shaped stacks need configuration that lives outside the file, dependencies that start in order, and the discipline to keep secrets out of version control.
⚔️ Skills You’ll Forge in This Chapter
- Externalizing configuration with environment variables and an
.envfile - Controlling startup order with
depends_onand health checks - Assembling a complete three-service application
🏗️ Environment Variables and the .env File
Hard-coding values in compose.yaml is fine for harmless defaults, but secrets and per-environment settings belong in an .env file that you never commit. Compose loads .env from the project directory automatically and substitutes ${VAR} references.
.env (add it to .gitignore):
POSTGRES_PASSWORD=super-secret-change-me
APP_PORT=8080
compose.yaml referencing it:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
env_file:
- .env
depends_on:
db:
condition: service_healthy # wait until db is healthy, not just started
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
volumes:
db-data:
depends_on alone only controls start order - it does not wait for a service to be ready. Pairing it with condition: service_healthy and a healthcheck makes the web service wait until Postgres can actually accept connections, eliminating a whole class of flaky-startup bugs.
🏗️ The Full Stack in Action
# Validate and view the fully-resolved configuration (great for debugging)
docker compose config
# Start everything; web waits for db to be healthy
docker compose up -d
# Scale a stateless service to three replicas behind the same network
docker compose up -d --scale web=3
# Tear down, removing volumes too for a truly clean slate
docker compose down -v
You now have a reproducible, version-controlled, multi-service environment that any teammate can launch with a single command - the foundation every CI/CD pipeline and orchestration platform builds upon.
🔍 Knowledge Check: Config & Startup
- Why should the
.envfile be listed in.gitignore? - What does
depends_onguarantee, and what does it NOT guarantee on its own? - How does
condition: service_healthychange startup behavior?
🎮 Mastery Challenges
🟢 Novice Challenge: Two-Service Stack
Objective: Reproduce the web + Redis stack from Chapter 1 and confirm the visit counter increments.
Requirements:
- A
compose.yamlwith a builtwebservice and a pulledredisservice - The web service reaches Redis by service name
docker compose up -dstarts both
Validation: Reloading http://localhost:8080 increases the counter.
🟡 Intermediate Challenge: Add a Persistent Database
Objective: Replace (or add) a Postgres service with a named volume and prove the data survives a teardown.
Requirements:
- A
dbservice usingpostgreswith a named volume - Credentials supplied through an
.envfile, not hard-coded - Demonstrate
downthenupkeeps the data
Validation: Write a row, run docker compose down (without -v), up again, and the row is still there.
🔴 Advanced Challenge: Health-Gated Three-Service App
Objective: Build a web + db + cache stack where the web service waits for the database to be healthy before starting.
Requirements:
- Three services on appropriate networks
- A
healthcheckon the database andcondition: service_healthyon the web service - All configuration via
.env;docker compose configresolves cleanly
Validation: Stopping then starting the stack never produces a “database not ready” error from the web service.
🏆 Quest Rewards & Achievements
🎖️ Badges Earned:
- 🏆 Stack Conductor - You orchestrated a multi-container app with a single command
- 🔗 Service Weaver - You connected services over networks and persisted data with volumes
🛠️ Skills Unlocked:
- Multi-Container Orchestration - Coordinate many containers as one application
- Declarative Stack Configuration - Describe infrastructure as a versioned file
🔓 Unlocked Quests:
- CI/CD Pipeline Basics - Run these stacks automatically on every commit
- Container Registries Deep Dive - Ship your stack’s images to production
📊 Progression Points: +50 XP
🗺️ Next Steps in Your Journey
Continue the Main Story:
- 🎯 Level 0101 - CI/CD & DevOps - Automate building and deploying your containerized stacks
Explore Side Adventures:
- ⚔️ Frontend Docker - Containerize a Jekyll + Bootstrap site
- ⚔️ Container Fundamentals - Revisit images, layers, and registries
Character Class Recommendations
💻 Software Developer: Advance toward CI/CD pipelines for your stacks
🏗️ System Engineer: Study networks, health checks, and resource limits in depth
🛡️ Security Specialist: Explore secrets management and network segmentation
📚 Resources
Official Documentation
- Docker Compose Overview - The official introduction
- Compose File Reference - Every key explained
- Compose Networking - Service discovery and custom networks
Community Resources
- Awesome Compose - Ready-made multi-service examples
- Docker Hub - Find official images for databases and caches
- Stack Overflow: docker-compose - Q&A when you get stuck
Learning Materials
- Use Compose in Development - Practical how-to guides
- Environment Variables in Compose - The .env file and substitution
🤝 Quest Completion Checklist
- ✅ Completed all primary objectives
- ✅ Started a multi-service stack with a single command
- ✅ Answered all knowledge check questions
- ✅ Completed at least one mastery challenge
- ✅ Explored the resource library
- ✅ Identified your next quest in the journey
🕸️ Knowledge Graph
Structured wiki-links connect this quest to the IT-Journey knowledge graph. Open the Obsidian Graph View to explore connections.
Level hub: [[Level 0100 - Frontend & Containers]] Overworld: [[🏰 Overworld - Master Quest Map]] Requires: [[Docker Container Fundamentals: Master Isolation & Portability for DevOps]] Obsidian docs: [[Obsidian Knowledge Graph and Wiki Links]]
🎁 Rewards
Badges
- 🏆 Stack Conductor - Orchestrated a multi-container app with a single command
- 🔗 Service Weaver - Connected services over networks and persisted data with volumes
Skills unlocked
- 🛠️ Multi-Container Orchestration
- 🧩 Declarative Stack Configuration
Features unlocked
- Readiness for the CI/CD & DevOps quest line at Level 0101
🕸️ Quest Network
Referenced by
- Loading…