Files
Knowledge-Base/40 - Guides/containers/docker-compose-patterns.md

157 lines
3.8 KiB
Markdown

---
title: Docker Compose Patterns
description: Reusable patterns for structuring Docker Compose applications in homelab and development environments
tags:
- containers
- docker
- compose
category: containers
created: 2026-03-14
updated: 2026-03-14
---
# Docker Compose Patterns
## Introduction
Docker Compose defines multi-container applications in a single declarative file. It is a good fit for homelab stacks, local development, and small self-hosted services that do not require a full orchestrator.
## Purpose
Compose helps when you need:
- Repeatable service definitions
- Shared networks and volumes for a stack
- Environment-specific overrides
- A clear deployment artifact that can live in Git
## Architecture Overview
A Compose application usually includes:
- One or more services
- One or more shared networks
- Persistent volumes
- Environment variables and mounted configuration
- Optional health checks and startup dependencies
## Step-by-Step Guide
### 1. Start with a minimal Compose file
```yaml
services:
app:
image: ghcr.io/example/app:1.2.3
ports:
- "8080:8080"
```
Start it:
```bash
docker compose up -d
docker compose ps
```
### 2. Add persistent storage and configuration
```yaml
services:
app:
image: ghcr.io/example/app:1.2.3
ports:
- "8080:8080"
environment:
APP_BASE_URL: "https://app.example.com"
volumes:
- app-data:/var/lib/app
volumes:
app-data:
```
### 3. Add dependencies with health checks
```yaml
services:
db:
image: postgres:16
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- db-data:/var/lib/postgresql/data
app:
image: ghcr.io/example/app:1.2.3
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
ports:
- "8080:8080"
volumes:
db-data:
```
## Common Patterns
### Use one project directory per stack
Keep the Compose file, `.env` example, and mounted config together in one directory.
### Use user-defined networks
Private internal services should communicate over Compose networks rather than the host network.
### Prefer explicit volumes
Named volumes are easier to back up and document than anonymous ones.
### Use profiles for optional services
Profiles are useful for dev-only services, one-shot migration jobs, or optional observability components.
## Troubleshooting Tips
### Services start in the wrong order
- Use health checks instead of only container start order
- Ensure the application retries database or dependency connections
### Configuration drift between hosts
- Commit the Compose file to Git
- Keep secrets out of the file and inject them separately
- Avoid host-specific bind mount paths when portability matters
### Containers cannot resolve each other
- Check that the services share the same Compose network
- Use the service name as the hostname
- Verify the application is not hard-coded to `localhost`
## Best Practices
- Omit the deprecated top-level `version` field in new Compose files
- Keep secrets outside the Compose YAML when possible
- Pin images to intentional versions
- Use health checks for stateful dependencies
- Treat Compose as deployment code and review changes like application code
## References
- [Docker: Compose file reference](https://docs.docker.com/reference/compose-file/)
- [Docker: Compose application model](https://docs.docker.com/compose/intro/compose-application-model/)
- [Docker: Control startup and shutdown order in Compose](https://docs.docker.com/compose/how-tos/startup-order/)
- [Compose Specification](https://compose-spec.io/)