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

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:

  1. Write a docker-compose.yml file that lists all the apps (services) you want.
  2. Run docker compose up -d to start everything in the background.
  3. Start using the apps!
  4. When you need changes, edit the YAML file.
  5. Use docker compose restart <service_name> to restart a specific app, or docker 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!