Docker Compose describes a multi-container app in one compose.yaml and brings it up with a single command — the backbone of most self-hosted setups.
A minimal stack
services:
app:
image: ghcr.io/example/app:latest
restart: unless-stopped
environment:
DB_HOST: db
env_file: .env # secrets here (see Secrets Management)
depends_on: [db]
ports: ["127.0.0.1:8080:8080"]
db:
image: mariadb:11
restart: unless-stopped
volumes: ["dbdata:/var/lib/mysql"]
environment:
MARIADB_DATABASE: app
volumes:
dbdata:
docker compose up -d # start in background
docker compose logs -f app # tail logs
docker compose pull && docker compose up -d # update
Good habits
| Habit | Why |
|---|---|
restart: unless-stopped |
Survives reboots |
Bind to 127.0.0.1 |
Don't expose ports publicly — front with a reverse proxy |
| Named volumes | Persist data across recreates |
.env + .gitignore |
Keep secrets out of the repo (Secrets Management) |
Harden the containers too — see Container and Docker Security.
Related: Reverse Proxy with Caddy and Nginx · Container and Docker Security · Database Backups and Replication