A reference repository showcasing how I like to manage my home lab infrastructure — treating personal infra with the same rigor you'd expect in a production environment. Everything is versioned, automated where it makes sense, and documented properly.
What's here:
stacks/— Docker Compose stacks deployed via Portainerterraform/— Infrastructure as Code modules for various providersdocs/— ADRs, golden paths, runbooks, playbooks, and architecture diagrams
| Layer | What | Tools |
|---|---|---|
| Compute | Proxmox VE hosts running LXC containers | Terraform (terraform/proxmox/) |
| Containers | Docker stacks managed via Portainer | Compose files in stacks/ |
| Networking | MikroTik RouterOS config | Terraform (terraform/routeros/) |
| Edge | Traefik reverse proxy + CrowdSec | stacks/traefik/ |
| Identity | Authentik (OAuth, SAML, LDAP) | stacks/authentik/ + Terraform |
| Inventory | NetBox for IPAM/DCIM | stacks/netbox/ + Terraform (terraform/netbox/) |
| Secrets | HashiCorp Vault + Vaultwarden | stacks/vault/, stacks/vaultwarden/ |
| Monitoring | Uptime Kuma, Grafana Synthetic Agent | stacks/uptime_kuma/, stacks/grafana-synthetic-agent/ |
| Media | Jellyfin + *arr stack + downloaders | stacks/mediabox/ |
📖 Full documentation — ADRs, golden paths, runbooks, playbooks, and C4 architecture diagrams.
NetBox serves as the single source of truth for infrastructure inventory:
- What it tracks: Sites, devices, device types, manufacturers, IP addresses, prefixes, VLANs, tags
- How it fits: The
terraform/netbox/module manages NetBox objects as code, keeping the inventory in sync with reality - Access: Internal only —
https://netbox.your-domain.local(placeholder)
The Terraform module (terraform/netbox/) defines:
- Device roles and types
- Manufacturers
- Sites and locations
- IP prefixes and addresses
- Tags for organization
| Icon | Service | Purpose |
|---|---|---|
| AdGuard Home | Network-wide DNS + adblocking | |
| Authentik | Identity & Access Management (OAuth, SAML, LDAP) | |
| Bazarr | Subtitle automation | |
| Calibre | eBook management & library | |
| Cloudflared | Tunnel to Cloudflare for remote access | |
| CUPS | Print server | |
| DIUN | Docker image update notifications | |
| Dozzle | Docker container log viewer | |
| FlareSolverr | Cloudflare bypass for indexers | |
| Gluetun | VPN gateway for "download stuff" apps | |
| Grafana Synthetic Agent | Uptime & performance monitoring | |
| Jellyfin | Media server | |
| Jellyseerr | Requests / discovery for Jellyfin | |
| n8n | Workflow automation platform | |
| netboot.xyz | Network boot environments | |
| NetBox | Network infrastructure IPAM | |
| PostgreSQL | Database server | |
| Prowlarr | Indexer manager | |
| qBittorrent | Torrent client (routed via VPN) | |
| Radarr | Movie automation | |
| SABnzbd | Usenet downloader (routed via VPN) | |
| Sonarr | TV automation | |
| Traefik | Reverse proxy with CrowdSec security integration | |
| Upsnap | Wake-on-LAN management | |
| Uptime Kuma | Uptime monitoring & status page | |
| Vault | Secrets management | |
| Vaultwarden | Password manager server (Bitwarden compatible) | |
| Warrtracker | Warranty & asset tracking | |
| watchyourlan | LAN device discovery & monitoring |
- ISP: INEA (Poland) — 300Mb/s synchronous FTTH
- Router: MikroTik hAP ac3
- Wireless: TP-Link Deco M4R x3 (mesh)
- Remote Access: Public IP with WireGuard / ngrok (backup)
IP Allocation:
| Range | Purpose |
|---|---|
.0–.9 |
Network devices |
.10–.99 |
Static IPs |
.100–.199 |
DHCP pool |
.200–.254 |
Homelab interfaces |
DHCP + NTP handled via RouterOS.
- Main Server (lake-1): Mini PC FIREBAT T8 Pro Plus — Intel N100, 16GB DDR5, 512GB SSD
- Runs Proxmox VE
- Hosts containers and VMs
- Secondary Server (edge-1): Dell PowerEdge R610 (experimental)
- Runs Proxmox VE
- Mostly off, sometimes runs a Minecraft server for my brother
LXC Containers on Proxmox:
- Docker (Portainer host)
- AdGuard
- HomeAssistant
- NAS: Synology DS220+ with 2 × 4TB disks
- Primary storage + backups
- Apple ecosystem (MacBook, iPhone, iPad, etc.)
- Android devices
- eBook readers (Kobo/Kindle)
- PCs with Windows 11/Fedora
- Samsung TVs
- Raspberry Pi (SDR Radio, 3D Printers)
- Printers: Sharp MX-4071, OKI ES5461
Each Terraform module under terraform/ has a Makefile with standard targets:
cd terraform/<module>
make init # Initialize Terraform
make plan # Preview changes
make apply # Apply changes (auto-approve)
make destroy # Tear down (auto-approve)
make validate # Validate configuration
make fmt # Format files
make check # Run validate + fmt
make clean # Remove .terraform/ and lock filesSome modules have extra targets (e.g., terraform/portainer/ has sync-portainer to rsync stacks to the host).
Stacks live in stacks/<service>/ with:
compose.yaml— the Docker Compose file*.env.example— example environment variables (copy to*.envand fill in secrets)- Optional config files (e.g.,
traefik.yaml,dynamic.yaml)
To deploy a stack:
- Copy
*.env.exampleto*.envand fill in secrets - Either:
- Use Portainer to deploy the stack, or
- Run
docker compose up -ddirectly on the host
The terraform/portainer/ module handles syncing stacks to the Portainer host via rsync.
.env.examplefiles: Committed to the repo — contain structure and placeholder values.envfiles: Never committed — contain actual secrets (gitignored)- Sensitive Terraform variables: Stored in
defaults.auto.tfvarsor passed via environment
├── docs/ # Documentation
│ ├── index.md # Docs portal
│ ├── adr/ # Architecture Decision Records
│ ├── golden-paths/ # How-to guides for common tasks
│ ├── runbooks/ # Incident response procedures
│ ├── playbooks/ # Repeatable operational tasks
│ ├── architecture/ # C4 diagrams and system overviews
│ └── assets/ # Icons and images
│
├── stacks/ # Docker Compose stacks
│ ├── adguard/
│ ├── authentik/
│ ├── calibre/
│ ├── cloudflared/
│ ├── cups/
│ ├── diun/
│ ├── dozzle/
│ ├── grafana-synthetic-agent/
│ ├── mediabox/
│ ├── n8n/
│ ├── netbootxyz/
│ ├── netbox/
│ ├── postgres/
│ ├── traefik/
│ ├── upsnap/
│ ├── uptime_kuma/
│ ├── vault/
│ ├── vaultwarden/
│ ├── warrtracker/
│ └── watchyourlan/
│
├── terraform/ # Infrastructure as Code
│ ├── authentik/
│ ├── backblaze/
│ ├── gcp/
│ ├── netbox/
│ ├── portainer/
│ ├── proxmox/
│ ├── routeros/
│ ├── synology-nas/
│ └── terraform-cloud/
│
├── mkdocs.yml # MkDocs configuration
└── README.md
- Portainer stacks:
- authentik
- cloudflared
- cups
- dozzle
- grafana-synthetic-agent
- mediabox
- n8n
- netboot.xyz
- postgres
- traefik
- upsnap
- uptime kuma
- watchyourlan
- Terraform:
- Portainer:
- All stacks created
- Users & Groups
- Host Settings
- Proxmox:
- Host settings
- LXC: Portainer
- LXC: AdGuard
- RouterOS:
- Interfaces
- Firewall rules
- DNS
- DHCP
- NTP
- WireGuard
- Authentik:
- General settings
- LDAP setup
- OIDC providers
- SAML integration
- Groups and users
- Portainer:
- Future:
- Ansible playbooks for host configuration
- Migrate cloud drives to NAS
- Migrate backups from Proxmox to NAS
- Use Authentik LDAP for Synology
- Add NUT/UPS integration
- k3s single-node cluster
- Self-hosted LLM (Ollama)
- Separated subnets (IoT isolation)
README cleanup: the "Apps on Portainer" table now actually lists everything that's sitting in stacks/ (adguard/keycloak/openldap included). Also expanded the whole mediabox setup into the individual apps (Jellyfin, *arrs, downloaders, VPN gateway), so it's not a mystery box anymore. Pulled proper icons into docs/assets/ for the mediabox apps too (incl. FlareSolverr) — README is now local-images-only.
Ripped out ZITADEL and went back to Authentik after realizing it was just causing more headaches than it was worth. Re-added Authentik service with full Docker Compose setup, updated PostgreSQL config to handle Authentik DB creds properly, and cleaned up all the ZITADEL cruft from Terraform. Also added Diun for Docker image update notifications. Basically reverting back to what was working before.
Wrote up new repo rules for Docker Compose stacks and Terraform/GCP configs, gutted the unused ArgoCD/Cert-Manager/chart cruft, and rewired the mediabox/Traefik/Portainer pieces: Configarr is gone, Vaultwarden got its own stack plus DB creds, and Terraform-side references now match the fresh stack layout. Lost some commits while switching PCs before I could push them (the HDD format nuked the old machine), so this is the history that actually made it up here.
Swapped Authentik with ZITADEL due to ongoing configuration issues. Despite limited documentation for ZITADEL, it's an actively growing project with promising potential.
Switched back from Podman to Docker due to too many compatibility issues. Docker has better ecosystem support and fewer configuration headaches.
Upgraded hardware by replacing old wally-1 terminal with new lake-1 server (Mini PC FIREBAT T8 Pro Plus Intel N100 16GB DDR5 / 512GB) for better performance. Also migrated domain from wally.dominiksiejak.pl to lake.dominiksiejak.pl, updating 25+ configuration files across Docker stacks, Helm charts, and Terraform modules. Updated all Traefik routing, SSL certificates, DNS, and service URLs.
Major infrastructure improvements: added n8n for workflow automation, PostgreSQL for database storage, and Grafana Synthetic Monitoring Agent. Enhanced development tools with pre-commit hooks, GitHub Actions, and improved CI/CD pipeline. Improved Traefik routing and security, updated Docker images, added healthchecks. Removed mktxp stack and .cursorrules file. Updated various configurations and documentation.
Replaced Nginx Proxy Manager with Traefik as the reverse proxy and added CrowdSec security integration. Added mediabox stack for home media server setup. Removed VictoriaMetrics stack since moving to Grafana Cloud. Updated all stack configurations to use Traefik labels.
Refactored the Terraform code slightly to reduce repetition. I also removed 'watchtower' — it's a great tool, but I prefer to update everything manually, and RenovateBot will handle the rest.
I have configured the Google SSO for Authentik with the terraform. However, I did some changes manually in the UI for the flows/stages, as they're super difficult to configure via terraform.
I have just refactored Portainer stacks, so I won't have to deal with many resources, but have done it all in a single array. Before, I thought it would be better this way, but it wasn't worth it.
I'm reverting from Portainer's GitOps option that was automatically pulling stacks from this repository. The delay between commits and actual changes in Portainer was frustrating. Plus, I couldn't make any temporary changes in stacks. I'd rather execute terraform apply to keep everything in one place.
Because I accidentally deleted my authentik environment variables, I decided to give authelia a shot. I've heard only good things about it, and it's fully configurable via YAML files. It seemed promising, but I felt like I was missing some options that worked perfectly out-of-the-box in authentik. I tried to configure it, but ended up restoring authentik from backup anyway.