Docker Compose Structure - Complete Guide¶
- This comprehensive lab covers the complete structure and syntax of Docker Compose files.
- You’ll learn about all the major configuration options, from basic service definitions to advanced features.

CTRL + click to open in new window
Description¶
This comprehensive lab covers the complete structure and syntax of Docker Compose files. You’ll learn about all the major configuration options, from basic service definitions to advanced features like health checks, networks, and volumes.
Prerequisites¶
- Completion of previous labs (001-intro, 002-Compose-Demo)
- Basic understanding of Docker concepts
- Familiarity with YAML syntax
Lab Overview¶
This comprehensive guide covers the complete structure and anatomy of Docker Compose files. You’ll master every aspect from basic service definitions to advanced production-ready configurations.
Table of Contents¶
- Docker Compose Structure - Complete Guide
- Description
- Prerequisites
- Lab Overview
- Table of Contents
- Common Docker Compose Service Features
- Feature Details
Example of a docker-compose.yml file structure¶
- Here is a simple example of a
docker-compose.ymlfile structure:
version: "3.8" # Compose file format version
services:
# Here we define our services (containers)
myservice:
# configurations for myservice
another_service:
# configurations for another_service
volumes:
# Global volume definitions (if any)
networks:
# Global network definitions (if any)
Common Docker Compose Service Features¶
Now, let’s detail the most common features you can use within each service definition:
| Feature | Description |
|---|---|
build | Specifies the build context or Dockerfile for creating container images. |
command | Overrides the default command defined in the image. |
configs | Defines configuration files for services. Configs let services to adapt their behavior without the need to rebuild a Docker image. |
container_name | Assigns a custom name to the container. |
depends_on | Defines service dependencies, controlling startup order. |
entrypoint | Overrides the default entry point defined in the image. |
environment | Sets environment variables for services. |
healthcheck | Defines commands to check the health of a service. |
image | Specifies an existing Docker image to use. |
links | (Legacy) Links services; replaced by networks. |
networks | Defines custom networks for your containers. |
ports | Maps container ports to host machine ports. |
restart | Configures restart policies for containers (e.g., always, on-failure). |
secrets | Defines secrets for sensitive data (e.g., passwords, API keys). |
version | Specifies the Compose file format version. |
volumes | Defines volumes used for persistent data storage. |
x-<feature_name> | Custom extension fields for reusable configurations. |
Feature Details¶
version¶
- Purpose: Specifies the Compose file format version. Each version has specific features and rules. It’s recommended to use recent 3.x versions.
- Example:
services¶
- Purpose: The top-level key that defines all the different containers that make up your application. Each key under
servicesis your service name. - Example:
build (Building an Image)¶
- Purpose: Instead of using an existing image,
buildinstructs Compose to build a new image from aDockerfilelocated at the specified path. - Options:
context: The path to the directory containing theDockerfileand other files needed for the build.dockerfile(optional): The name of the Dockerfile if it’s notDockerfile(e.g.,Dockerfile.dev).args(optional): Build arguments that will be passed to theDockerfileduring the build process (likeARG).target(optional): A specific build stage to target from a multi-stageDockerfile.- Example:
services:
webapp:
build:
context: ./my_app # Builds an image from the 'my_app' directory (where Dockerfile resides)
dockerfile: Dockerfile.dev # Uses Dockerfile.dev file
args:
NODE_VERSION: "18"
ports:
- "80:80"
image (Using an Existing Image)¶
- Purpose: Instructs Compose to pull and use an existing Docker image from a registry like Docker Hub.
- Example:
services:
database:
image: postgres:15 # Uses the Postgres image at version 15
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
command and entrypoint¶
command:- Purpose: Defines the command that will be executed when the container starts. This command overrides the
CMDdefined in the image’sDockerfile. -
Example:
```yaml services: worker: image: my_worker_image command: ["python", "worker.py", "--queue", "high"] # Executes a Python script with arguments ``` -
entrypoint: - Purpose: Defines the entry point for the container. The
command(orCMDfrom theDockerfile) will be passed as an argument to theentrypoint. This overrides theENTRYPOINTdefined in theDockerfile. - Example:
```yaml services: script_runner: image: ubuntu entrypoint: ["bash", "-c"] # Entry point will be running bash command: ["echo Hello from Docker Compose!"] # The command will be passed as an argument to bash -c ```
ports (Port Mapping)¶
- Purpose: Maps ports from the container to ports on the host machine, allowing access to the service from outside the container.
- Formats:
"HOST_PORT:CONTAINER_PORT": Maps a specific port."CONTAINER_PORT": A random available host port will be mapped toCONTAINER_PORT.- Example:
services:
web:
image: nginx:latest
ports:
- "80:80" # Host port 80 maps to container port 80
- "443:443" # Host port 443 maps to container port 443
- "8080" # Container port 8080 maps to a random host port
volumes (Volume Management)¶
- Purpose: Allows for persistent data storage, ensuring data remains even if the container is recreated or removed. You can map files/directories from the host machine into the container (bind mounts) or use Docker-managed volumes (named volumes).
- Formats:
"HOST_PATH:CONTAINER_PATH": Bind mount."VOLUME_NAME:CONTAINER_PATH": Named volume.- Example:
services:
db:
image: postgres:15
volumes:
- db_data:/var/lib/postgresql/data # Named volume for persistent DB data
- ./app/config:/etc/app/config # Bind mount: maps host folder to container (for config files)
volumes: # Global volume definitions db_data: driver: local # Volume driver (default) # You can also add options or driver_opts for advanced configurations, # e.g., driver_opts: {type: nfs, o: “addr=192.168.1.100,nolock,rw”, device: “:/export/data”} # but this is less common for a simple guide. ```
environment (Environment Variables)¶
- Purpose: Defines environment variables for the container. Useful for passing configuration details like passwords, API keys, or environment settings (development/production).
- Formats:
- List of
KEY=VALUEstrings. - Map of
KEY: VALUEpairs. - Example:
services:
api:
image: my_api_image
environment:
API_KEY: your_api_key
DATABASE_URL: postgres://user:password@db:5432/mydb
NODE_ENV: development
- DEBUG=true # Another format for lists
Tip: For sensitive variables, consider using a .env file (in the same directory as your docker-compose.yml) or Docker Secrets.
depends_on (Service Dependencies)¶
- Purpose: Defines the startup order between services. Services will only start after the services they depend on have been started. Important:
depends_onensures the container has started, but not necessarily that the service within it is ready to accept connections (e.g., a database fully booted and listening). - Example:
services:
web:
image: my_web_app
depends_on:
- api # The web service will start only after the api service has started
- db # And the db service has started
ports:
- "80:80"
api:
image: my_api_service
depends_on:
- db # The api service will start only after the db service has started
ports:
- "3000:3000"
db:
image: postgres:15
networks (Defining Networks)¶
- Purpose: Allows you to define custom networks so that services can communicate with each other. Services on the same network can communicate using their service names (DNS resolution).
- Example:
services:
web:
image: nginx
ports:
- "80:80"
networks:
- frontend_network # Connected to frontend_network
- backend_network # Connected to backend_network
api:
image: my_api_app
networks:
- backend_network # Connected to backend_network
db:
image: postgres:15
networks:
- db_network # Connected to db_network
networks: # Global network definitions
frontend_network:
# driver: bridge (default)
backend_network:
# driver: bridge
db_network:
internal: true # Internal network only (not accessible from the host)
# ipam:
# config:
# - subnet: 172.20.0.0/24 # Defines an IP range for the network for the
links (Linking Services - Legacy)¶
- Purpose: Allows linking older services (pre-Compose v2) and provides aliases for hostnames. It is highly recommended to use
networksinstead oflinksin newer Compose versions (v2 and above), aslinksis considered legacy. - Example (Not recommended for new usage):
services:
web:
image: my_web_app
links:
- db:database_alias # Links to 'db' and gives it an alias 'database_alias'
container_name (Custom Container Name)¶
- Purpose: Allows you to specify a custom name for the running container, instead of the name Docker Compose automatically generates (usually
project_service_index). - Example:
services:
my_web:
image: nginx
container_name: my_nginx_server # The container will be named 'my_nginx_server'
ports:
- "80:80"
restart (Restart Policy)¶
- Purpose: Determines how the container will be restarted if it exits or crashes.
- Options:
no: Does not restart (default).on-failure: Restarts only if the container exits with a non-zero exit code.always: Always restarts, even if the container exits successfully.unless-stopped: Always restarts unless it is explicitly stopped.- Example:
healthcheck (Health Check)¶
- Purpose: Defines how Docker can check if the container (and the service within it) is healthy and functional. This is particularly useful in conjunction with
depends_onto ensure a dependent service is truly ready before other services connect to it. - Options:
test: The command to be executed for the health check.interval: How often to run the check (default: 30s).timeout: Maximum time allowed for a single check to complete (default: 30s).retries: How many consecutive failures are needed to consider the container unhealthy (default: 3).start_period: Initialization time to allow a container to bootstrap. During this period, health check failures will not count towards the maximum retries (default: 0s).- Example:
services:
web:
image: my_web_app
ports:
- "80:80"
depends_on:
api:
condition: service_healthy # The web service will only start after 'api' is healthy
healthcheck:
# Checks if web server responds
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
interval: 10s
timeout: 5s
retries: 3
# Give the service 20 seconds to start up initially
# before starting health checks
start_period: 20s api:
image: my_api_service
ports:
- "3000:3000"
healthcheck:
# Checks a health endpoint
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 5s
timeout: 3s
retries: 5
Complete docker-compose.yml Example¶
Here’s an example combining many of the features discussed, illustrating a simple web application with a backend API and a database:
version: "3.8"
services:
web:
build:
context: ./frontend # Assumes a frontend application in ./frontend with its Dockerfile
dockerfile: Dockerfile
container_name: my-webapp-frontend
ports:
- "80:80"
environment:
API_URL: http://api:3000 # Connects to the 'api' service within the Docker network
networks:
- app_network
depends_on:
api:
condition: service_healthy # Ensures API is healthy before starting web
restart: unless_stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
api:
build:
context: ./backend # Assumes a backend API application in ./backend with its Dockerfile
container_name: my-api-backend
ports:
- "3000:3000"
environment:
DB_HOST: db # Connects to the 'db' service within the Docker network
DB_USER: user
DB_PASSWORD: password
DB_NAME: mydatabase
NODE_ENV: production
volumes:
- api_logs:/var/log/my_app # Mounts a named volume for API logs
networks:
- app_network
- db_network # Connects to both app and db networks
depends_on:
db:
condition: service_healthy # Ensures DB is healthy before starting API
restart: on_failure
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 15s
timeout: 5s
retries: 5
start_period: 15s
db:
image: postgres:15
container_name: my-postgres-db
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data # Mounts a named volume for persistent DB data
networks:
- db_network # Only connected to the database network
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydatabase"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data: # Definition of the named volume for database data
api_logs: # Definition of the named volume for API logs
networks:
app_network: # Network for web and API communication
db_network: # Network for API and DB communication (can be internal for better isolation)
internal: true # Makes this network only accessible by containers connected to it, not from the host directly
Hands-On Tasks¶
Task 1: Service Configuration¶
Create a docker-compose.yml with a single web service using different configuration options.
version: "3.8"
services:
web:
image: nginx:alpine
container_name: my-web-server
ports:
- "8080:80"
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
restart: unless-stopped
# 1. Save the YAML above as docker-compose.yml
docker compose up -d
# 2. Verify the container name
docker compose ps
# 3. Check environment variables
docker compose exec web env | grep NGINX
# 4. Verify port mapping
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080
# 5. Clean up
docker compose down
Expected Outcome: A single nginx service runs with custom naming, port mapping, and environment variables.
Task 2: Volumes and Data Persistence¶
Practice different volume types: bind mount, named volume.
version: "3.8"
services:
web:
image: nginx:alpine
volumes:
- ./html:/usr/share/nginx/html
- nginx_logs:/var/log/nginx
ports:
- "8080:80"
volumes:
nginx_logs:
mkdir -p html
echo "<h1>Hello from bind mount</h1>" > html/index.html
docker compose up -d
curl http://localhost:8080
docker volume ls | grep nginx_logs
echo "<h1>Updated content</h1>" > html/index.html
curl http://localhost:8080
docker compose down --volumes
rm -rf html
Expected Outcome: Bind mounts reflect live file changes; named volumes persist.
Task 3: Networks and Service Discovery¶
Create two services on isolated networks.
version: "3.8"
services:
app:
image: nginx:alpine
networks:
- frontend
- backend
ports:
- "8080:80"
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
networks:
- backend
networks:
frontend:
backend:
internal: true
docker compose up -d
docker compose exec app sh -c "nc -zv db 5432"
docker network ls | grep "002-structure"
docker compose down --volumes
Expected Outcome: Services communicate via DNS names across networks.
Task 4: Health Checks and Dependencies¶
Implement health checks with controlled startup order.
version: "3.8"
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: healthcheck_pass
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
app:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
db:
condition: service_healthy
Expected Outcome: The app service waits for the DB healthcheck to pass before starting.
Task 5: Extension Fields (x-*)¶
Use YAML extension fields for reusable configuration blocks.
version: "3.8"
x-logging: &logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
x-resources: &resources
deploy:
resources:
limits:
cpus: "0.5"
memory: "256M"
services:
web:
image: nginx:alpine
<<: [*logging, *resources]
ports:
- "8080:80"
redis:
image: redis:7-alpine
<<: [*logging, *resources]
Expected Outcome: Extension fields reduce duplication across services.
Task 6: Restart Policies¶
Test different restart policies.
version: "3.8"
services:
always-restart:
image: alpine
restart: always
command: sh -c "sleep 3 && exit 1"
no-restart:
image: alpine
restart: "no"
command: sh -c "sleep 3 && exit 1"
Expected Outcome: Services with always restart after crash; no stays stopped.
Task 7: Build Context¶
Build a custom image from a Dockerfile.
docker compose build
docker images | grep my-custom-app
docker compose up
docker compose down
docker rmi my-custom-app
rm -rf app
Expected Outcome: Custom images are built from Dockerfiles using the build directive.
Verification Checklist¶
- I understand service configuration (image, ports, environment, restart)
- I can use bind mounts and named volumes
- I can create custom networks with isolation
- I can implement health checks with depends_on conditions
- I can use extension fields (x-*) for reusable config
- I understand how restart policies behave
- I can build custom images with the build directive
