- Python 64.6%
- Shell 28.9%
- Jinja 6.5%
| ansible | ||
| apps | ||
| argocd | ||
| runbooks | ||
| scripts | ||
| .gitignore | ||
| README.md | ||
Homelab
GitOps-managed Kubernetes homelab powered by ArgoCD, provisioned with Ansible.
Quick Start
From bare metal
# One-command bootstrap: prepare nodes, install k3s, deploy ArgoCD
ansible-playbook ansible/install-all.yml
See ansible/README.md for full details on playbooks, roles, and ordering.
From an existing cluster
# 1. Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 2. Wait for ArgoCD to be ready
kubectl wait -n argocd --for=condition=Ready pods --all --timeout=5m
# 3. Apply the root app
kubectl apply -f argocd/root-app.yaml
# 4. (Optional) Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
That's it. The root app watches the argocd/ directory, which deploys three category-level apps (infra, media, productivity), which in turn deploy all individual services. Everything syncs automatically — push to main and ArgoCD applies it.
Access Services
All services are exposed at {name}.example.lab (internal) or {name}.example.cloud (external via Cloudflare) via Traefik. Add *.example.lab to your DNS (Pi-hole, router, or /etc/hosts pointing to your cluster's ingress IP).
Repository Layout
argocd/ # Root app watches here
root-app.yaml # Bootstrap: applied once via kubectl
infra.yaml # Deploys apps/infra/
media.yaml # Deploys apps/media/
productivity.yaml # Deploys apps/productivity/
apps/
infra/ # Infrastructure: Traefik, cert-manager, Technitium, etc.
media/ # Media: Jellyfin, Immich, *arr stack, etc.
productivity/ # Productivity: Nextcloud, Forgejo, n8n, Synapse, etc.
scripts/ # Utility scripts (config backups, etc.)
Active Services
| Category | Service | Hostname | Status |
|---|---|---|---|
| Infra | Traefik (ingress) | — | ✅ |
| cert-manager | — | ✅ | |
| sealed-secrets | — | ✅ | |
| MetalLB | — | ✅ | |
| Technitium DNS | technitium.example.lab | ✅ | |
| Authentik | authentik.example.cloud | ✅ | |
| Prometheus | — | ✅ | |
| Loki | — | ✅ | |
| Grafana | grafana.example.internal | ✅ | |
| Longhorn | storage.example.internal | ✅ | |
| RustFS (S3) | — | ✅ | |
| Home Assistant | ha.example.cloud | ✅ | |
| ArgoCD | argocd.example.internal | ✅ | |
| Media | Jellyfin | jellyfin.example.cloud | ✅ |
| Immich | photos.example.cloud | ✅ | |
| Audiobookshelf | audiobooks.example.cloud | ✅ | |
| Navidrome | music.example.cloud | ✅ | |
| Komga | books.example.cloud | ✅ | |
| Seerr | seerr.example.cloud | ✅ | |
| Sonarr | sonarr.example.lab | ✅ | |
| Radarr | radarr.example.lab | ✅ | |
| Lidarr | lidarr.example.lab | ✅ | |
| Readarr | readarr.example.lab | ✅ | |
| Bazarr | bazarr.example.lab | ✅ | |
| Prowlarr | prowlarr.example.lab | ✅ | |
| Gluetun (VPN) | — | ✅ | |
| BookBounty | bookbounty.example.lab | ✅ | |
| Productivity | Nextcloud | nextcloud.example.cloud | ✅ |
| Ollama | ollama.example.cloud | ✅ | |
| Open WebUI | ai.example.cloud | ✅ | |
| Outline wiki | wiki.example.cloud | ✅ | |
| Homarr dashboard | homarr.example.cloud | ✅ | |
| Sure (finance) | finance.example.cloud | ✅ | |
| Forgejo (Git) | git.example.cloud | ✅ | |
| n8n automation | n8n.example.lab | ✅ | |
| Synapse (Matrix) | element.example.cloud | ✅ | |
| SearXNG search | search.example.cloud | ✅ |
Storage
- NFS Media (
nfs-mediaStorageClass): Static NFS PVs backed by a Synology NAS. Each app gets its own PV/PVC pair for the media mount at/data. - Synology CSI (
synology-*-*StorageClasses): ISCSI and SMB volumes provisioned on demand from the Synology NAS, withRetainreclaim policy. - Longhorn (
longhorn— default for config PVCs): Replicated block storage across both nodes (2 replicas). All app config/data PVCs use this class — survives pod reschedules and ArgoCD redeploys.
Secrets
Secrets use sealed-secrets. Encrypted SealedSecret manifests live alongside each app. They're decrypted automatically by the sealed-secrets controller at sync time.
Notes
- The control node may need taints removed to run workloads:
kubectl taint nodes --all node-role.kubernetes.io/control-plane- - Config PVCs use Longhorn with 2 replicas, so data persists across redeploys. For migration-level backups, see
scripts/backup-*-configs.shorscripts/authentik-migrate.py. - NFS-backed media mounts are stateless from the cluster's perspective — the Synology owns the data.