📑 Contents Mac mini as a server Recommended stack Phase 1 — Foundation Phase 2 — Containers Phase 3 — Core Services Phase 4 — Networking Phase 5 — Monitoring Phase 6 — Storage & Backup Core services + FileBrowser Advanced services Media + Shoko + Bazarr Reverse proxy & TLS Remote access Security hardening References

Why homelab?

A homelab is a personal server environment you control entirely. The Mac mini is an ideal choice: it's quiet, power-efficient (~20W idle), runs macOS natively, has excellent Apple Silicon performance, and doesn't require a loud rack or a separate NAS.

GoalWhat you get
PrivacyYour data never leaves your network — no cloud subscriptions, no data mining
LearningReal hands-on experience with Docker, networking, Linux services, TLS, monitoring
Cost savingsReplace Dropbox, Bitwarden, GitHub, Notion, Netflix infra with self-hosted equivalents
SpeedLocal services are faster than cloud for LAN clients (streaming, file sync, DNS)
ReliabilityYou control uptime, not a third-party SLA
Start small, grow gradually Don't try to set up everything at once. Follow the phases in this guide — get one layer solid before adding the next. A working foundation beats a half-configured mess.

Mac mini as a server

Which Mac mini?

ModelVerdictNotes
Mac mini M4 (2024)Best choice16 GB base RAM, excellent per-watt perf, 3 USB-A + 3 USB-C/TB. M4 Pro variant for heavy workloads.
Mac mini M2 / M2 ProGreatStill fast, good value used. M2 Pro has 10-core CPU + more RAM options.
Mac mini M1 (2020)Good enough8 GB RAM is limiting. Fine for lightweight services; avoid if running 10+ containers.
Mac mini Intel (2018)Avoid for new setupHigher power draw, no Rosetta benefit, no future macOS support roadmap.

RAM guidance

  • 8 GB — basic homelab: DNS, VPN, dashboard, password manager, reverse proxy
  • 16 GB — comfortable: adds monitoring stack, Nextcloud, Git server, media server
  • 24–32 GB (M2/M4 Pro) — heavy: multiple databases, LLM inference, K3s cluster, full *arr stack

Storage

  • Internal SSD for OS + Docker images + small databases
  • External USB-C SSD or NAS for large media, backups, Time Machine
  • WD My Passport / Samsung T7 (portable SSD) — cheap and fast for 2–4 TB
  • Synology or QNAP NAS if you need RAID redundancy and > 8 TB

macOS-specific advantages

  • No noisy fan — runs silently at idle, fine for a living room or office
  • Wake on LAN / Wake on network access — built into System Settings → Energy
  • Time Machine — excellent backup solution already built in
  • Rosetta 2 — runs x86 Docker images on Apple Silicon transparently
  • SMB sharing — built-in Samba server via System Settings → Sharing

Recommended Stack Overview

Access
Tailscale VPN Cloudflare Tunnel Local LAN
Proxy / TLS
Caddy (auto HTTPS) or Nginx Proxy Manager
Dashboard
Homepage
Core Services
AdGuard Home Vaultwarden Gitea Nextcloud Uptime Kuma FileBrowser
Monitoring
Prometheus Grafana Loki node_exporter
Media
Jellyfin Shoko (anime) Sonarr / Radarr / Bazarr Prowlarr qBittorrent or RDTClient FlareSolverr
Runtime
OrbStack (Docker) Docker Compose
Host OS
macOS (Mac mini)
Why Docker Compose instead of Kubernetes? For a single Mac mini homelab, Docker Compose is the right tool. It's simple, well-documented, and has no overhead. K3s is worth exploring later if you add more machines or want to practice Kubernetes.

Phase 1 — Foundation (macOS Setup)

1
Static IP / Reserve DHCP
Router level — never let your server's IP change

On your router, find the Mac mini's MAC address and assign it a reserved/static DHCP IP (e.g., 192.168.1.10). Every service will be reachable at this address consistently.

2
Enable SSH Remote Login
System Settings → General → Sharing → Remote Login
# From your laptop — test SSH works
ssh youruser@192.168.1.10

# Set a hostname so it's easy to identify
sudo scutil --set HostName macmini
sudo scutil --set LocalHostName macmini
sudo scutil --set ComputerName macmini

# Copy your SSH key (no password prompts)
ssh-copy-id youruser@192.168.1.10
3
Prevent Sleep
System Settings → Battery (or Energy Saver)

Go to System Settings → Battery → Options and set:

  • Prevent automatic sleeping when the display is off → ON
  • Wake for network access → ON
  • Start up automatically after a power failure → ON
# Also via CLI
sudo pmset -a sleep 0
sudo pmset -a disksleep 0
sudo pmset -a womp 1     # Wake on network access
sudo pmset -a autorestart 1
4
Install Homebrew + Essential Tools
Package manager for macOS
# Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Essential CLI tools
brew install git curl wget htop ncdu jq yq tmux
brew install --cask visual-studio-code   # optional GUI
5
Set Up Directory Structure
Organize all your homelab configs consistently
# Suggested layout — all your service configs live here
mkdir -p ~/homelab/{caddy,adguard,vaultwarden,gitea,nextcloud,monitoring,media,homepage}
mkdir -p ~/homelab/monitoring/{prometheus,grafana,loki}
mkdir -p ~/homelab/media/{jellyfin,sonarr,radarr,prowlarr,qbittorrent}

# Data directories — put on external drive if you have one
mkdir -p /Volumes/data/{nextcloud,jellyfin,gitea,vaultwarden}

Phase 2 — Container Runtime (Docker)

OrbStack (Recommended for Mac)

OrbStack is the fastest Docker runtime on macOS. It uses Apple's Virtualization framework, starts in ~2 seconds, has the lowest RAM/CPU overhead, and includes a native Mac menu bar app.

# Install OrbStack
brew install --cask orbstack

# Verify Docker works
docker run --rm hello-world
docker compose version
OrbStack vs Colima OrbStack is the best experience on Mac (paid after trial, ~$8/mo or $96/yr). Colima is the best free alternative. Both work great for homelab use. See the macOS VM & Containers study page for a full comparison.

Docker Compose project layout

Structure each service as its own docker-compose.yml so you can start/stop them independently.

~/homelab/
├── caddy/
│   ├── docker-compose.yml
│   ├── Caddyfile
│   └── data/           # auto TLS certs, Caddy state
├── adguard/
│   ├── docker-compose.yml
│   └── data/
├── vaultwarden/
│   ├── docker-compose.yml
│   └── data/
└── monitoring/
    ├── docker-compose.yml
    ├── prometheus/
    │   └── prometheus.yml
    └── grafana/
        └── provisioning/

Shared Docker network

Create a shared bridge network so containers in different Compose projects can reach each other (e.g., Caddy talking to Vaultwarden).

# Create once
docker network create homelab

# In each docker-compose.yml, add:
networks:
  homelab:
    external: true

services:
  vaultwarden:
    image: vaultwarden/server:latest
    networks:
      - homelab
    # ... rest of config

Phase 3 — Core Services

AdGuard Home — DNS + Ad Blocking

Set this up first. Point your router's DNS to the Mac mini IP, and every device on your network gets ad blocking with zero client config. AdGuard Home is more polished than Pi-hole.

# ~/homelab/adguard/docker-compose.yml
services:
  adguard:
    image: adguard/adguardhome:latest
    container_name: adguard
    volumes:
      - ./data/work:/opt/adguardhome/work
      - ./data/conf:/opt/adguardhome/conf
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "3000:3000"   # setup wizard (first run only)
      - "80:80"       # admin UI after setup
    networks:
      - homelab
    restart: unless-stopped

networks:
  homelab:
    external: true
Port 53 on macOS macOS runs its own DNS responder on port 53. You may need to disable it first: sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist — or map AdGuard to a different port and point your router there.

Caddy — Reverse Proxy with Auto HTTPS

Caddy automatically obtains and renews TLS certificates. Point your local DNS entries (or /etc/hosts) to the Mac mini and get HTTPS for every service.

# ~/homelab/caddy/Caddyfile
{
  email you@example.com
}

# Local-only services (uses self-signed or internal CA)
vault.home {
  reverse_proxy vaultwarden:80 {
    header_up X-Real-IP {remote_host}
  }
}

git.home {
  reverse_proxy gitea:3000
}

grafana.home {
  reverse_proxy grafana:3000
}

adguard.home {
  reverse_proxy adguard:80
}
# ~/homelab/caddy/docker-compose.yml
services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./data:/data
      - ./config:/config
    networks:
      - homelab
    restart: unless-stopped

networks:
  homelab:
    external: true
Local DNS trick In AdGuard Home → Filters → DNS Rewrites, add entries like vault.home → 192.168.1.10. Now every device on your LAN resolves https://vault.home to your Mac mini. Caddy handles TLS with its internal CA — import Caddy's root cert into your devices once.

Vaultwarden — Self-hosted Password Manager

Vaultwarden is a Bitwarden-compatible server written in Rust. Use the official Bitwarden browser extension and mobile app pointed at your self-hosted instance.

# ~/homelab/vaultwarden/docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    environment:
      WEBSOCKET_ENABLED: "true"
      SIGNUPS_ALLOWED: "false"  # disable after creating your account
      ADMIN_TOKEN: "your-long-random-token-here"
    volumes:
      - /Volumes/data/vaultwarden:/data
    networks:
      - homelab
    restart: unless-stopped

Gitea / Forgejo — Self-hosted Git

Run your own Git server. Mirror your GitHub repos here, host private projects, or use it as a CI/CD backend with Gitea Actions.

# ~/homelab/gitea/docker-compose.yml
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      USER_UID: "1000"
      USER_GID: "1000"
      GITEA__database__DB_TYPE: sqlite3
    volumes:
      - /Volumes/data/gitea:/data
    ports:
      - "2222:22"   # SSH for git push
    networks:
      - homelab
    restart: unless-stopped

Homepage — Dashboard

A clean, fast dashboard to see all your services at a glance. Supports service status checks, weather, bookmarks, and Docker integration.

# ~/homelab/homepage/docker-compose.yml
services:
  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    volumes:
      - ./config:/app/config
      - /var/run/docker.sock:/var/run/docker.sock:ro  # for Docker widget
    networks:
      - homelab
    restart: unless-stopped

Phase 4 — Networking & Remote Access

1
Tailscale (Easiest — Start Here)
Zero-config WireGuard mesh VPN

Tailscale creates a secure mesh VPN between your devices. Install it on the Mac mini and your phone/laptop — you can access every homelab service from anywhere as if you were on your LAN. Free for personal use (up to 100 devices).

# Install on Mac mini
brew install --cask tailscale

# Start + authenticate
tailscale up

# Enable subnet routing (expose your whole LAN to Tailscale clients)
tailscale up --advertise-routes=192.168.1.0/24 --accept-routes

# On your router — approve route in Tailscale admin console
# Then on your laptop:
tailscale up --accept-routes
2
Cloudflare Tunnel (Public access, no port forwarding)
Expose services to the internet without opening router ports

If you want some services reachable from the internet (e.g., Vaultwarden for mobile app sync), Cloudflare Tunnel creates an outbound connection — no port forwarding, no public IP needed.

# Install cloudflared
brew install cloudflare/cloudflare/cloudflared

# Login and create a tunnel
cloudflared tunnel login
cloudflared tunnel create homelab

# config.yml — map public hostnames to local services
tunnel: <tunnel-id>
credentials-file: ~/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: vault.yourdomain.com
    service: http://localhost:80   # Caddy proxy
  - service: http_status:404

# Run as a launchd service (starts on boot)
sudo cloudflared service install
Only expose what's necessary Don't expose everything publicly. Keep dashboards, Grafana, and admin UIs LAN-only (or Tailscale-only). Only expose services you actively need from the internet (Vaultwarden, Nextcloud).
3
Dynamic DNS (if using home IP directly)
Keep a domain pointed at your changing home IP

If you're not using Cloudflare Tunnel and prefer direct port forwarding, you need DDNS to handle your ISP's dynamic IP.

# Option A: Cloudflare DDNS (if your domain is on Cloudflare)
docker run -d \
  -e CF_API_TOKEN=your_token \
  -e DOMAINS=home.yourdomain.com \
  --restart unless-stopped \
  favonia/cloudflare-ddns:latest

# Option B: DuckDNS (free dynamic DNS service)
docker run -d \
  -e SUBDOMAINS=yourlabel \
  -e TOKEN=your-duckdns-token \
  --restart unless-stopped \
  lscr.io/linuxserver/duckdns:latest

Phase 5 — Monitoring Stack

The standard homelab monitoring stack: Prometheus collects metrics, Grafana visualizes them, Loki aggregates logs. node_exporter exposes host-level metrics from the Mac mini.

macOS note for node_exporter The standard Prometheus node_exporter runs inside a Linux container and sees the container's resources, not macOS host metrics. For real Mac mini CPU/memory/disk metrics, run node_exporter natively on macOS via Homebrew.
# Install node_exporter natively on macOS
brew install node_exporter

# Start as a background service (survives reboots)
brew services start node_exporter

# Verify metrics at:
curl http://localhost:9100/metrics | head -20
# ~/homelab/monitoring/prometheus/prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: node
    static_configs:
      - targets: ["host.docker.internal:9100"]  # macOS host from container

  - job_name: caddy
    static_configs:
      - targets: ["caddy:2019"]

  - job_name: adguard
    static_configs:
      - targets: ["adguard:9617"]   # adguard-exporter sidecar
# ~/homelab/monitoring/docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
    networks:
      - homelab
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "changeme"
      GF_USERS_ALLOW_SIGN_UP: "false"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - homelab
    restart: unless-stopped

  loki:
    image: grafana/loki:latest
    container_name: loki
    volumes:
      - loki_data:/loki
    networks:
      - homelab
    restart: unless-stopped

  promtail:
    image: grafana/promtail:latest
    container_name: promtail
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - homelab
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:
  loki_data:

networks:
  homelab:
    external: true

Recommended Grafana dashboards

  • Node Exporter Full — Dashboard ID 1860 — CPU, memory, disk, network for the Mac mini
  • Caddy dashboard — request rates, latency, error rates per service
  • AdGuard Exporter — queries blocked, top blocked domains, DNS latency
  • Docker containers — Dashboard ID 11600 — per-container resource usage

Uptime Kuma — Status page

A simple, beautiful uptime monitor for all your services. Shows response time history and sends alerts (email, Telegram, Slack).

services:
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    volumes:
      - uptime_data:/app/data
    networks:
      - homelab
    restart: unless-stopped

Phase 6 — Storage & Backups

The 3-2-1 backup rule

  • 3 copies of your data
  • 2 different storage media/locations
  • 1 copy offsite (cloud or remote)

Time Machine — macOS host backup

# Time Machine via CLI — backup to an external drive
sudo tmutil setdestination /Volumes/backup

# Exclude Docker data (large, rebuildable)
tmutil addexclusion ~/.orbstack
tmutil addexclusion ~/Library/Containers/com.docker.docker

Nextcloud — File sync & cloud replacement

Self-hosted Dropbox/Google Drive replacement. Sync files from all your devices to the Mac mini.

services:
  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    environment:
      NEXTCLOUD_ADMIN_USER: admin
      NEXTCLOUD_ADMIN_PASSWORD: changeme
      NEXTCLOUD_TRUSTED_DOMAINS: "cloud.home"
    volumes:
      - /Volumes/data/nextcloud:/var/www/html
    networks:
      - homelab
    restart: unless-stopped

Offsite backup with Backblaze B2

# Install rclone
brew install rclone

# Configure Backblaze B2
rclone config
# → New remote → b2 → enter your B2 key/secret

# Sync critical data to B2 (run via cron or launchd)
rclone sync /Volumes/data/vaultwarden b2:my-homelab-backup/vaultwarden
rclone sync /Volumes/data/gitea b2:my-homelab-backup/gitea

# Add to crontab — run at 3am daily
crontab -e
0 3 * * * /opt/homebrew/bin/rclone sync /Volumes/data b2:my-homelab-backup

Docker volume backup

# Backup a named Docker volume to a tarball
docker run --rm \
  -v prometheus_data:/data \
  -v /Volumes/backup/docker:/backup \
  alpine tar czf /backup/prometheus_data.tar.gz /data

# Script to backup all volumes
for vol in prometheus_data grafana_data loki_data; do
  docker run --rm \
    -v ${vol}:/data \
    -v /Volumes/backup/docker:/backup \
    alpine tar czf /backup/${vol}-$(date +%Y%m%d).tar.gz /data
done

Core Services Catalog

🛡️ AdGuard Home MUST
DNS server + ad blocker. Set as router's DNS — covers every device on LAN automatically.
:80 web UI · :53 DNS · :3000 setup
🔒 Vaultwarden MUST
Self-hosted Bitwarden password manager. Use official Bitwarden clients pointed at your server.
:80 (behind Caddy) · SQLite DB
🌐 Caddy MUST
Reverse proxy with automatic HTTPS. One config file to expose all services with TLS.
:80 HTTP · :443 HTTPS · :2019 metrics
📊 Homepage MUST
Beautiful homelab dashboard with service status, widgets, bookmarks, and Docker integration.
:3000
🐙 Gitea / Forgejo GREAT
Self-hosted Git. Private repos, mirrors from GitHub, Gitea Actions for CI/CD.
:3000 web · :2222 SSH
☁️ Nextcloud GREAT
Self-hosted Dropbox + Google Docs. File sync, calendar, contacts, office suite.
:80 (behind Caddy)
📡 Uptime Kuma GREAT
Status monitor for all your services. HTTP/TCP ping checks, Telegram/email alerts.
:3001
📦 Portainer GREAT
Web UI for managing Docker containers, images, volumes, networks. Great for visual management.
:9000 HTTP · :9443 HTTPS
📁 FileBrowser GREAT
Web-based file manager for your homelab storage. Browse, upload, download, edit files from any browser — no Nextcloud needed for simple access.
:8080

FileBrowser — docker-compose

# ~/homelab/filebrowser/docker-compose.yml
services:
  filebrowser:
    image: filebrowser/filebrowser:latest
    container_name: filebrowser
    environment:
      FB_BASEURL: "/files"   # serve at /files path (optional)
    volumes:
      - /Volumes/data:/srv          # root of what's browseable
      - ./filebrowser.db:/database/filebrowser.db
      - ./settings.json:/.filebrowser.json
    networks:
      - homelab
    restart: unless-stopped

# Add to Caddyfile:
files.home {
  reverse_proxy filebrowser:8080
}

Advanced Services

🔑 Authentik GREAT
Self-hosted SSO / identity provider. Add OAuth/OIDC login to all your services. Alternative to Keycloak (lighter).
:9000 · :9443 · requires PostgreSQL + Redis
📝 Outline OPTIONAL
Self-hosted team wiki / knowledge base. Notion alternative. Great for homelab documentation.
:3000 · requires PostgreSQL + Redis + S3
🤖 Ollama + Open WebUI OPTIONAL
Run LLMs locally (Llama 3, Mistral, etc.) on your Mac mini. M-series Macs are excellent for local inference.
:11434 API · :8080 Web UI
🌐 FreshRSS OPTIONAL
Self-hosted RSS reader. Follow blogs, news, YouTube channels without an algorithm.
:80 (behind Caddy)
🔗 Linkwarden OPTIONAL
Self-hosted bookmark manager with full-page archiving. Never lose a link when a page goes down.
:3000 · requires PostgreSQL
📧 Stalwart Mail OPTIONAL
Modern all-in-one mail server (SMTP+IMAP+JMAP). Self-host your email — advanced setup.
:25 SMTP · :993 IMAP · :443 webmail

Media Stack

The *arr ecosystem automates TV/movie downloads and organizes your library. Jellyfin serves it to your devices. Add Shoko for anime-specific management, Bazarr for automatic subtitles, and RDTClient + FlareSolverr for premium debrid-based downloads.

Media servers

🎬 Jellyfin MEDIA
Free, open-source media server. Stream movies, TV, music, photos to any device. No subscription unlike Plex.
:8096 HTTP · :8920 HTTPS
🌸 Shoko Server MEDIA
Anime-specific library manager. Automatically identifies anime using AniDB, manages episodes, groups, and metadata. Works alongside Jellyfin via Shoko plugin.
:8111 · Shoko Desktop on :13011

*arr automation stack

📺 Sonarr MEDIA
Automated TV show management. Monitors, downloads, and renames TV episodes automatically.
:8989
🎥 Radarr MEDIA
Movie management. Integrates with download clients to grab movies automatically.
:7878
💬 Bazarr MEDIA
Automatic subtitle management. Monitors Sonarr/Radarr libraries and downloads matching subtitles from OpenSubtitles, Subscene, etc.
:6767
🔍 Prowlarr MEDIA
Indexer manager for *arr apps. Configure torrent/usenet indexers in one place, syncs to all *arr apps.
:9696

Download clients

⬇️ qBittorrent MEDIA
Standard torrent download client. The *arr apps push magnet/torrent links here.
:8080 web UI · :6881 torrent
RDTClient MEDIA
Real-Debrid download client. Downloads via Real-Debrid's premium servers (faster, no seeding, no ISP throttling). *arr apps see it as a standard torrent client.
:6500
🛡️ FlareSolverr MEDIA
Proxy server to bypass Cloudflare protection on torrent indexers. Prowlarr calls FlareSolverr automatically for protected indexer sites.
:8191
qBittorrent vs RDTClient — which to use? qBittorrent is free but you're seeding on your home IP. RDTClient + Real-Debrid (~€3/mo) downloads from RD's servers — much faster (often 100 MB/s+), no seeding, no ISP throttling or DMCA notices. If you're serious about the media stack, Real-Debrid is worth it. Both can coexist — point Radarr at RDTClient for new movies, qBittorrent for niche content not on RD.

RDTClient docker-compose

# ~/homelab/media/docker-compose.yml (excerpt)
services:
  rdtclient:
    image: rogerfar/rdtclient:latest
    container_name: rdtclient
    environment:
      PUID: "1000"
      PGID: "1000"
    volumes:
      - /Volumes/data/downloads:/data/downloads
      - ./rdtclient:/data/db
    networks:
      - homelab
    restart: unless-stopped

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    environment:
      LOG_LEVEL: info
    networks:
      - homelab
    restart: unless-stopped

  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: Asia/Bangkok
    volumes:
      - ./bazarr/config:/config
      - /Volumes/data:/data
    networks:
      - homelab
    restart: unless-stopped

Shoko + Jellyfin setup

# ~/homelab/media/shoko/docker-compose.yml
services:
  shoko:
    image: ghcr.io/shokoanime/shokoserver:latest
    container_name: shoko
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: Asia/Bangkok
    volumes:
      - ./shoko/config:/home/shoko/.shoko
      - /Volumes/data/anime:/anime     # your anime library
    networks:
      - homelab
    restart: unless-stopped

# After setup:
# 1. Connect Shoko to your anime folder
# 2. Install Shoko Metadata plugin in Jellyfin
# 3. Point Jellyfin anime library to /anime
# 4. Jellyfin now uses Shoko for all anime metadata

*arr stack volume layout

# All *arr services share the same media directory
# This lets Radarr/Sonarr do instant hardlinks (no copy)
/Volumes/data/
├── downloads/         # qBittorrent / RDTClient downloads here
│   ├── complete/
│   └── incomplete/
├── movies/            # Radarr moves completed downloads here
├── tv/                # Sonarr moves completed downloads here
├── anime/             # Shoko manages this folder
└── music/

# All containers mount the SAME /data path — hardlinks work across arr apps
services:
  radarr:
    volumes:
      - /Volumes/data:/data
  rdtclient:
    volumes:
      - /Volumes/data/downloads:/data/downloads
  jellyfin:
    volumes:
      - /Volumes/data:/data

Reverse Proxy & TLS Details

Local HTTPS with Caddy's internal CA

For LAN-only services, Caddy can act as its own CA and issue certificates. You import Caddy's root cert into your devices once, and all local services get trusted HTTPS.

# Tell Caddy to use internal TLS for .home domains
vault.home {
  tls internal
  reverse_proxy vaultwarden:80
}

# Get the root cert from Caddy's data directory
# ~/homelab/caddy/data/caddy/pki/authorities/local/root.crt

# Import it on your Mac
sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain \
  ~/homelab/caddy/data/caddy/pki/authorities/local/root.crt

# On iOS: AirDrop the .crt file → Settings → General → VPN & Device Management → Install

Let's Encrypt for public domains

# Caddy auto-obtains Let's Encrypt certs for public domains
vault.yourdomain.com {
  reverse_proxy vaultwarden:80
}
# That's it — Caddy handles cert issuance + renewal automatically

Alternative: Nginx Proxy Manager (GUI approach)

If you prefer clicking over editing config files, Nginx Proxy Manager (NPM) gives you a web UI to manage all proxy hosts, SSL certs, and access lists. This is what the dashboard in the example screenshot uses.

CaddyNginx Proxy Manager
Config methodText file (Caddyfile)Web UI (click-based)
Auto HTTPS✓ Zero config✓ Built-in Let's Encrypt UI
Learning curveLow (simple syntax)Very low (GUI)
Version control✓ Caddyfile in git✗ Config in SQLite DB
Internal CA✓ Built-in✗ Not supported
Best forCode-first, git-backedBeginners, quick setup
# ~/homelab/nginx-proxy-manager/docker-compose.yml
services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    ports:
      - "80:80"
      - "443:443"
      - "81:81"    # Admin UI
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - homelab
    restart: unless-stopped

# Default login: admin@example.com / changeme
# Then: Hosts → Proxy Hosts → Add Proxy Host
#   Domain: vault.home  →  Forward to: vaultwarden:80
#   SSL tab: Request new cert → Let's Encrypt

Remote Access Options

MethodSetupBest forCost
Tailscale 5 min Personal use, all services via VPN Free (personal)
Cloudflare Tunnel 15 min Public-facing services, no port forwarding Free
WireGuard (self-managed) 1–2 hr Full control, no third-party Free (VPS for endpoint ~$5/mo)
Port forwarding + DDNS 30 min Direct access, ISP allows it Free (if ISP allows)
ZeroTier 10 min Alternative to Tailscale, self-hosted controller option Free (25 devices)
Recommended combo Use Tailscale for personal remote access (phone, laptop) + Cloudflare Tunnel for the 1–2 services you want publicly reachable (Vaultwarden sync, Nextcloud mobile). This gives you the best security posture with zero exposed router ports.

Security Hardening

  • Disable password SSH, use key auth only
    Edit /etc/ssh/sshd_config: PasswordAuthentication no
  • Change default SSH port
    Set Port 2222 in sshd_config — stops automated scans on port 22
  • Enable macOS Firewall
    System Settings → Network → Firewall → ON. Block all incoming except what you explicitly allow.
  • Disable SSH password auth on all containers too
    Gitea, etc. — disable password login in their web UIs
  • Set SIGNUPS_ALLOWED=false on Vaultwarden
    After creating your account, immediately disable public registration
  • Use strong, unique Admin API keys / tokens
    Generate with openssl rand -hex 32. Store in Vaultwarden itself.
  • Never expose admin UIs publicly
    Portainer, Grafana, AdGuard, Prometheus → LAN or Tailscale only
  • Keep containers updated
    Run docker compose pull && docker compose up -d weekly. Use Watchtower for automation.
  • Use non-root users in Docker containers
    Add user: "1000:1000" where possible. Avoid privileged: true.
  • Enable macOS FileVault
    System Settings → Privacy & Security → FileVault. Encrypts the internal SSD.
  • Review open ports regularly
    Run sudo lsof -i -P | grep LISTEN to see what's exposed
# Watchtower — auto-updates containers (optional)
services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_SCHEDULE: "0 0 3 * * *"   # 3am daily
      WATCHTOWER_NOTIFICATIONS: "slack"
      WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: "https://hooks.slack.com/..."
    restart: unless-stopped

References

Official docs and community resources. All links verified as of 2026-06-28. Updated with Shoko, Bazarr, RDTClient, FlareSolverr, FileBrowser, Nginx Proxy Manager.

🐳
OrbStack Docs
docs.orbstack.dev
macOS Docker runtime — setup, networking, resource management.
🌐
Caddy Documentation
caddyserver.com/docs
Caddyfile syntax, automatic HTTPS, reverse proxy directives.
🔒
Tailscale Docs
tailscale.com/kb
VPN setup, subnet routing, MagicDNS, access controls.
🔑
Vaultwarden Wiki
github.com/dani-garcia/vaultwarden/wiki
Docker setup, reverse proxy config, backup, admin console.
🛡️
AdGuard Home Docs
adguard.com/en/adguard-home
DNS server setup, block lists, DoH/DoT configuration.
☁️
Cloudflare Tunnel Docs
developers.cloudflare.com
cloudflared setup, ingress rules, running as a service.
🐙
Gitea Documentation
docs.gitea.com
Installation, configuration, Gitea Actions CI/CD.
🎬
Jellyfin Docs
jellyfin.org/docs
Media server setup, hardware transcoding, library management.
📊
Grafana Docs
grafana.com/docs/grafana/latest
Dashboard creation, data sources, alerting rules.
🏠
Homepage Dashboard Docs
gethomepage.dev
Service cards, widgets, Docker integration, YAML config.
💾
rclone Docs
rclone.org/docs
Cloud backup sync to B2, S3, Google Drive, and 40+ providers.
🐧
LinuxServer.io Docs
docs.linuxserver.io
Well-maintained Docker images for Sonarr, Radarr, Prowlarr, Bazarr, and 100+ more.
🔀
Nginx Proxy Manager Docs
nginxproxymanager.com/guide
GUI-based reverse proxy. Setup, proxy hosts, SSL certs, access lists.
🌸
Shoko Anime Docs
docs.shokoanime.com
Anime library manager — Docker setup, Jellyfin plugin integration, AniDB matching.
💬
Bazarr Wiki
wiki.bazarr.media
Automatic subtitle downloader — setup, subtitle providers, Sonarr/Radarr integration.
RDTClient GitHub
github.com/rogerfar/rdt-client
Real-Debrid download client — setup with *arr apps, Docker config, troubleshooting.
🛡️
FlareSolverr GitHub
github.com/FlareSolverr/FlareSolverr
Cloudflare bypass proxy for protected indexers — configure in Prowlarr as a proxy.
📁
FileBrowser Docs
filebrowser.org
Web-based file manager — Docker setup, user management, custom base URL.