Docker Compose: My Go-To for Managing Apps on My Home Server

This entire blog you're reading is hosted using Docker Compose. Not just this blog though — my Immich media server, NAS monitoring stack, and other apps I run at home all rely on it too.
If you're new to Docker itself, check out my earlier post where I explain containers with a ship-and-container analogy.
Today, I want to dive deeper into Docker Compose — how it helps me manage complex app stacks with just a YAML file.
High-Level Workflow
Using Docker Compose is as easy as:
- Write a
docker-compose.yml
file that lists all the apps (services) you want. - Run
docker compose up -d
to start everything in the background. - Start using the apps!
- When you need changes, edit the YAML file.
- Use
docker compose restart <service_name>
to restart a specific app, ordocker compose down && docker compose up -d
to redeploy everything.
Anatomy of docker-compose.yml
Here are some key elements in every Compose file:
- services: These are the apps you want to run (e.g., Ghost, MySQL, Prometheus).
- container_name: Optional, but helps identify your containers.
- ports: Exposes container ports to your host, allowing you to access the service from outside.
- volumes: Mounts directories or files between your host and the container. Useful for persisting data or serving config files.
- environment: Set required environment variables directly. But I highly recommend using
env_file
for secrets and configs. - networks: Controls how services communicate with each other — isolated, shared, or with custom subnetting.

My Ghost Blog Stack
Here's a simplified version of my Ghost blog server stack:
services:
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/:ro
- ./nginx/log/:/var/log/nginx/
depends_on:
- ghost
ghost:
image: ghost:latest
env_file:
- .env
volumes:
- ghost_data:/var/lib/ghost/content/
- ./config.production.json:/var/lib/ghost/config.production.json
depends_on:
- db
db:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
MYSQL_DATABASE: ghost
MYSQL_USER: ghost-user
MYSQL_PASSWORD: ${USER_PASSWORD}
volumes:
mysql_data:
ghost_data:
I omitted the other parameters like networks and restart, just to make things easier.
Directory Tree
I store the files in a directory that looks like the diagram below:
ghost-stack/
├── docker-compose.yml
├── .env
├── config.production.json
├── nginx/
│ ├── conf.d/
│ └── log/
└── volumes/
├── ghost_data/
└── mysql_data/
All persistent content and logs are easily accessible from the host.
My Other Stack Examples
ImmichApp
Uses Redis, Postgres, and optional CUDA ML features.
immich/
├── docker-compose.yml
├── .env
└── volumes/
├── upload/
└── db_data/
Monitoring Stack
Includes Prometheus, Grafana, Node Exporter, Smartctl, and NVIDIA DCGM exporter — all configured for local dashboard access.
nas-monitoring/
├── docker-compose.yml
├── prometheus/
│ ├── prometheus.yml
│ └── alert.rules.yml
├── alertmanager/
│ └── config.yml
└── tmp/
Conclusion
I'm still on my journey of learning more about containerization.
Docker Compose made it possible for me to run full-fledged app stacks with minimal overhead. In future posts, I'll dive deeper into my monitoring stack, including how I set up alerts for Discord and monitor my NAS health in real-time.
If you're looking to self-host anything, give Docker Compose a try — it’s powerful, simple, and very addictive.
But until then, see you in the next post! Have a nice day!