ReviseAlgo Logo

Docker Compose — Multi-Container Apps

Writing a docker-compose.yml — Every Field Explained

Understanding services, networks, volumes, environment, and builds.

Interview: Be prepared to write a basic Docker Compose YAML file on a whiteboard. Understand how fields like `build`, `ports`, `depends_on` (including condition checks for database readiness), `environment`, and `volumes` are structured and function.

Last Updated: June 15, 2026 15 min read

Introduction

A Compose configuration file is written in YAML. The configuration file deconstructs into three root-level sections: services (containers to build/run), networks (communication networks), and volumes (persistent storage mounts).

Why It Matters

Writing structured YAML prevents configurations from breaking. Syntax errors or wrong indentation causes the parser to fail. Understanding database healthchecks and dependency conditioning ensures that applications start up in the correct sequence.

Real-World Analogy

A docker-compose.yml file is like a blueprints manual for building a town. The services block defines the buildings (e.g. shops, apartments, power plants). The networks block defines the roads and telephone cables connecting the buildings. The volumes block defines the permanent underground safes and water lines that remain intact even if the buildings are torn down and rebuilt.

How It Works

When parsing a YAML file, Docker maps keys to internal structures:

  • build: Directs Docker to invoke the compiler on local Dockerfiles.
  • ports vs expose: ports binds a container port to a host network port (publicly accessible), whereas expose opens container ports only within the private Compose virtual network (invisible to host physical cards).
  • depends_on: Establishes start sequencing. Coupled with condition: service_healthy, it delays startup until prerequisites pass health checks.

Internal Architecture

Docker compose executes commands in parallel where possible. If Service A depends on Service B, Compose blocks Service A launch until Service B container registers. The daemon constantly evaluates the health state of containers via standard exit codes or custom test scripts defined under the healthcheck key.

Visual Explanation

Loading diagram…

Practical Example

Here is a comprehensive docker-compose.yml file showcasing custom builds, storage mounts, database healthchecks, and dependency conditioning:

Common Mistakes

1. Tab Indentation: YAML does not support tab characters for indentation. You must use spaces (usually 2 spaces per indentation level) otherwise it will throw syntax parsing errors.
2. Weak depends_on: If you use depends_on: [database] without health conditions, the backend app starts the millisecond the database container process starts, which often leads to crashes because database processes take a few seconds to configure sockets and accept transactions.

Quick Quiz

Q1: Which directive exposes a container port to other containers on the same network but NOT to the host computer?

A) ports

B) expose

C) network_bind

Answer: B — expose registers the port within the Docker network space without creating port bindings on the host network interfaces.

Scenario-Based Challenge

Scenario:

Your company requires database passwords to be fed dynamically to backend containers. The security team forbids hardcoding passwords in YAML files. How would you solve this using Docker Compose?

View Solution

Create a local .env file containing key-value secrets (e.g. DB_PASS=supersecret). In the docker-compose.yml file, map the variable using syntax interpolation:

Docker Compose automatically reads the .env file in the current directory and injects these variables during container startup, keeping secrets out of version control.

Debugging Exercise

Failure Case:

An app crashes on startup throwing: health check failed for service "database". The YAML includes: test: ["CMD", "pg_isready"].

How would you debug this?

View Solution

The command pg_isready requires specifying database users or names when running on custom database ports.
Fix: Change the health check syntax to run in a shell environment, passing the admin user:

Interview Questions

1. Explain the difference between "ports" and "expose" in a Compose file.

ports binds a container port to a host machine interface, making it publicly reachable from outside the host. expose registers a port for container-to-container communication within the private Docker bridge network without binding it to the host network interface.

2. How do you implement database connection readiness wait inside Docker Compose?

By declaring a healthcheck block in the database service definition, and configuring the dependent app service to use depends_on with a condition status of service_healthy.

Production Considerations

In production configurations, specify explicit tags for images (e.g. postgres:15-alpine) rather than using the latest tag. Using latest makes builds non-deterministic and can pull breaking database changes unexpectedly.