This repository contains the Terraform and Docker configuration for my multi-site homelab setup, featuring automated CI/CD deployment, comprehensive monitoring, and networking.
The homelab consists of two geographically separated sites connected via VPN:
| Vieta Site (Primary) | Minerva Site (Secondary) |
|---|---|
|
|
Note
Long-term direction: migrate away from Docker Compose and run services via Helm charts on a Talos-based Kubernetes cluster (RPis), with the control plane hosted on an Intel NUC running Proxmox.
Future plans for Vieta Site include a 10-inch desktop rack equipped with custom 3D-printed mounting brackets to organize the hardware.
Each site implements a dual-VLAN architecture to separate domestic devices from infrastructure.
| VLAN Name | Type | Description |
|---|---|---|
| Default | Standard | General devices (Phones, Laptops, IoT) |
| Athena | Infrastructure | Dedicated homelab network for all services |
- VPN Tunnel: Secure connection between Athena VLANs across sites.
- NAS Sync: Automated data replication between Apollo and Zeus.
- Firewall Enforcement: Access to Athena network from Default VLAN is exclusively through Traefik reverse proxy. No direct access to homelab services bypassing the proxy
The homelab utilizes Terraform as the Infrastructure as Code (IaC) principles through Terraform to ensure a declarative, reproducible, and version-controlled foundation for the entire network and server stack.
Terraform is organized into numbered layers (each with its own backend state).
00-global/: Shared Terraform foundation (remote state backend, base provider config)01-network/: UniFi network state (VLANs, DHCP reservations, port profiles) + Cloudflare DNS02-infrastructure/: Proxmox + Talos bootstrap for my Kubernetes homelab03-services/: Helm charts deployed onto the cluster (planned / not implemented yet)
| Service | Description | URL | Purpose |
|---|---|---|---|
| Traefik | Reverse Proxy & Load Balancer | All *.lippok.dev |
SSL termination, routing, authentication |
| Portainer | Docker Management | port.lippok.dev |
Container orchestration and monitoring |
| Service | Description | URL | Metrics Port |
|---|---|---|---|
| Prometheus | Metrics Collection | prometheus.lippok.dev |
:9090 |
| Grafana | Data Visualization | grafana.lippok.dev |
:3000 |
| Alertmanager | Alert Management | alerts.lippok.dev |
:9093 |
| Gatus | Uptime Monitoring | gatus.lippok.dev |
:8080 |
| Service | Target | Port | Metrics |
|---|---|---|---|
| Node Exporter | Pi4 System Metrics | :9100 | CPU, Memory, Disk, Network |
| Node Exporter (Pi3) | Pi3 System Metrics | :9100 | CPU, Memory, Disk, Network |
| cAdvisor | Docker Container Metrics | :8080 | Container resources and performance |
| UnPoller (Local) | UniFi Controller (10.10.0.1) | :9130 | Local network statistics |
| UnPoller (Remote) | UniFi Controller (10.0.0.1) | :9131 | Remote network statistics |
| Mailcow Exporter | Mail Server (mail.lippok.eu) | :9099 | Email service metrics |
| FritzBox Exporter | Router (192.168.178.1) | :9787 | ISP connection and router stats |
| Service | Description | URL | Features |
|---|---|---|---|
| Homepage | Unified Dashboard | olympus.lippok.dev |
Service status, weather, quick access |
- Docker and Docker Compose installed
apache2-utilsfor password hashingenvsubstfor template processing
The setup uses environment variable substitution for configuration files that don't natively support .env files.
Create a .env file in src/Pi4/ with the following variables:
# Traefik Authentication
TRAEFIK_USER=admin
TRAEFIK_PASSWORD=<hashed_password>
TRAEFIK_PASSWORD_UNHASHED=<plain_password>
# Cloudflare DNS (for Let's Encrypt)
CLOUDFLARE_EMAIL=your-email@domain.com
CF_DNS_API_TOKEN=your_cloudflare_token
# Container Passwords
PORTAINER_PASSWORD=<hashed_password>
GRAFANA_ADMIN_PASSWORD=your_secure_password
# API Keys & Integration
HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY=your_weather_api_key
HOMEPAGE_VAR_MAILCOW_API_KEY=your_mailcow_api_key
HOMEPAGE_VAR_PORTAINER_API_KEY=your_portainer_api_key
# UniFi Credentials
HOMEPAGE_VAR_UNIFI_USER=unifi_username
HOMEPAGE_VAR_UNIFI_PASSWORD=unifi_password
# Router Credentials
FRITZBOX_USERNAME=fritz_username
FRITZBOX_PASSWORD=fritz_password
# Notification Webhooks
DISCORD_WEBHOOK_URL=your_discord_webhook_url
# Tailscale Configuration
TAILSCALE_AUTHKEY=your_tailscale_auth_keyGenerate hashed passwords for Traefik and Portainer:
# Install apache2-utils if not present
sudo apt-get install apache2-utils
# Generate password hash for Traefik/Portainer
htpasswd -nb admin your_password
# Copy the full output for TRAEFIK_PASSWORD
# For Portainer, extract just the hash part
htpasswd -nb -B admin your_password | cut -d ":" -f 2
# Copy the hash for PORTAINER_PASSWORDThe setup uses .template files that are processed with envsubst to inject environment variables:
traefik.yml.template→traefik.ymlprometheus.yml.template→prometheus.ymlalertmanager.yml.template→alertmanager.yml
The repository includes GitHub Actions for automated deployment of both Pi4 and Pi3 sites.
Note: To use this method, you must install custom GitHub Actions runners on your target machines.
Additionally, add all required environment variables as GitHub repository secrets.
- Fork the repository
- Install a custom GitHub Actions runner on your deployment target
- Configure GitHub Secrets with all required environment variables
- Push to main branch – deployment happens automatically
-
Clone the repository:
git clone https://github.com/FinnPL/Homelab-Setup.git cd Homelab-Setup/src/Pi4 -
Create and configure
.envfile:nano .env # Add all required environment variables -
Generate configuration files from templates:
# Export environment variables set -a && source .env && set +a # Process templates envsubst < traefik/traefik.yml.template > traefik/traefik.yml envsubst < prometheus/prometheus.yml.template > prometheus/prometheus.yml envsubst < alertmanager/alertmanager.yml.template > alertmanager/alertmanager.yml
-
Deploy the stack:
docker compose up -d
-
Configure Portainer API:
- Access Portainer at
https://port.lippok.dev - Navigate to User settings → API tokens
- Generate and copy the API key
- Add
HOMEPAGE_VAR_PORTAINER_API_KEYto.env - Restart homepage:
docker compose restart homepage
- Access Portainer at