A self-hosted homelab running on a single Lenovo ThinkCentre M720t, orchestrated
entirely with Docker Compose and a thin make wrapper. Each service is an
independent compose project; everything is reverse-proxied through Caddy with
automatic TLS, monitored, and backed up offsite.
This is a public showcase of my homelab configuration. Secrets live in gitignored env files (templates are provided), and a few private services are omitted. Domains are shown as
example.com— swap in your own.
- No Caddyfile — reverse-proxy routing is configured purely via Docker labels
using
caddy-docker-proxy, with TLS via the Cloudflare DNS challenge (no exposed ports for certs). - One make target per service —
make up-caddy,make logs-adguard,make restart-homepage, … plus aggregates (make up,make pull,make status). - Clean data separation — compose files and small configs live in git; app state, databases, and media live outside the repo and are never committed.
- Defense in depth — Tailscale for remote access, AdGuard Home for DNS-level filtering, CrowdSec for intrusion detection, minimal WAN exposure.
- Offsite backups —
~/datais backed up daily to a Hetzner Storage Box via Borgmatic, with healthcheck pings.
┌─────────────┐
Internet ───────▶│ Cloudflare │ (DNS + DNS-01 challenge)
└──────┬──────┘
│
┌──────▼──────┐
│ Caddy │ reverse proxy, automatic TLS
│ (labels → │ *.example.com
│ upstreams) │
└──────┬──────┘
┌─────────────┼───────────────┐
┌─────▼────┐ ┌─────▼─────┐ ┌──────▼──────┐
│ media │ │ homepage │ │ adguard… │ + many more
│ (Plex) │ │ dashboard │ │ (LAN IP) │
└──────────┘ └───────────┘ └─────────────┘
Remote access via Tailscale · Intrusion detection via CrowdSec
| Network | Type | Purpose |
|---|---|---|
proxy_network |
bridge 172.20.0.0/16 |
Service ↔ Caddy reachability |
lan_macvlan |
macvlan 192.168.0.240/28 |
AdGuard gets a real LAN IP |
network_mode: host |
— | Tailscale, CrowdSec, Home Assistant, Samba |
| Service | Role |
|---|---|
| Caddy | Reverse proxy + automatic TLS (label-driven, no Caddyfile) |
| Tailscale | Mesh VPN for remote access |
| AdGuard Home | Network-wide DNS + ad/tracker blocking (primary + Pi replica) |
Plex (media/) |
Media server with hardware transcoding (Intel Quick Sync) |
| Homepage | Single-pane dashboard with live service widgets |
| Uptime Kuma | Uptime/status monitoring |
| Glances | Host system metrics |
| Scrutiny | Disk S.M.A.R.T. health monitoring |
| CrowdSec | Crowd-sourced intrusion detection + firewall bouncer |
| Borgmatic | Encrypted, deduplicated offsite backups |
| Diun | Docker image update notifications |
| Samba | SMB file sharing + Time Machine target |
| Home Assistant | Home automation |
| Bitcoin node | Pruned Bitcoin full node |
| Snowflake | Tor Snowflake proxy (censorship circumvention) |
| Minecraft / V Rising | Game servers (manual start) |
homelab/
├── Makefile ← orchestration (one project per service)
├── .env.common ← shared non-secret constants (TZ, PUID, PGID) — committed
├── .env.example ← template for gitignored .env (external secrets)
├── .env.secrets.example ← template for gitignored .env.secrets
├── SETUP.md ← OS-level bootstrap notes
└── <service>/
├── compose.yaml ← the service definition
└── config/ ← small bind-mounted configs (where applicable)
App data (~/data/<service>/), media, and downloads live outside the repo
and are intentionally not committed.
# 1. Provide secrets (never committed)
cp .env.example .env && $EDITOR .env
cp .env.secrets.example .env.secrets && $EDITOR .env.secrets
# 2. Bring everything up (creates Docker networks first)
make up
# Per-service control
make up-caddy # start one service
make logs-adguard # tail logs
make restart-homepage
make status # list running containers
make help # all targets| What | Where | In git? |
|---|---|---|
| Compose files + small configs | <service>/ |
✅ |
| Shared non-secret constants | .env.common |
✅ |
| External secrets (Cloudflare token, app credentials) | .env |
❌ gitignored |
| High-sensitivity secrets (backup passphrase) | .env.secrets |
❌ gitignored |
| App runtime data (DBs, caches, metadata) | ~/data/<service>/ |
❌ |
| Media & downloads | /mnt/storage/ |
❌ |
- Server: Lenovo ThinkCentre M720t — Intel UHD 630 (Quick Sync hardware transcoding), MergerFS storage pool across multiple drives.
- Backup DNS: Raspberry Pi running a second AdGuard Home instance.
Generated as a sanitized public mirror of a private homelab repo.