36672d3f1066d32685791bbe451383f3d2ec255d
Some checks failed
Terraform / terraform (push) Failing after 8s
NetBird GitOps PoC
Proof-of-concept for managing NetBird VPN configuration via Infrastructure as Code (IaC) with GitOps workflow using Terraform.
Project Status: POC Complete
Start date: 2026-02-15
Status: Full automation implemented including peer auto-naming
What Works
- NetBird self-hosted instance deployed (
netbird-poc.networkmonitor.cc) - Gitea CI/CD server deployed (
gitea-poc.networkmonitor.cc) - Gitea Actions runner for CI/CD
- Terraform implementation - creates groups, policies, setup keys
- CI/CD pipeline - PR shows plan, merge-to-main applies changes
- Watcher service - automatically renames peers based on setup key names
Architecture
+-------------------+ PR/Merge +-------------------+
| Engineer | ----------------> | Gitea |
| (creates setup | | (CI/CD) |
| key: pilot-X) | +-------------------+
+-------------------+ |
| terraform apply
v
+-------------------+ +-------------------+
| Watcher Service | <---- polls ----> | NetBird API |
| (auto-rename) | +-------------------+
+-------------------+ ^
| enrolls
+-------------------+ |
| Operator | -------------------------+
| (uses setup key) | peer appears as "DESKTOP-XYZ"
+-------------------+ watcher renames to "pilot-X"
Complete Workflow
- Ticket arrives: "Onboard pilot Ivanov"
- Engineer adds to Terraform:
resource "netbird_setup_key" "pilot_ivanov" { name = "pilot-ivanov" # <-- This becomes the peer name type = "one-off" auto_groups = [netbird_group.pilots.id] usage_limit = 1 } - Engineer creates PR -> CI runs
terraform plan - PR merged -> CI runs
terraform apply-> setup key created - Engineer retrieves key:
terraform output -raw pilot_ivanov_key - Engineer sends key to operator (via secure channel)
- Operator enrolls -> peer appears as
DESKTOP-ABC123 - Watcher detects consumed key, renames peer to
pilot-ivanov - Done - peer is correctly named, no manual intervention
Directory Structure
netbird-gitops-poc/
├── ansible/ # Deployment playbooks
│ ├── caddy/ # Shared reverse proxy
│ ├── gitea/ # Standalone Gitea
│ ├── gitea-runner/ # Gitea Actions runner
│ ├── netbird/ # NetBird server
│ └── netbird-watcher/ # Peer renamer service
├── terraform/ # Terraform configuration
│ ├── .gitea/workflows/ # CI/CD workflow
│ ├── main.tf
│ ├── groups.tf
│ ├── policies.tf
│ ├── setup_keys.tf
│ └── outputs.tf
├── watcher/ # Watcher service source
│ ├── netbird_watcher.py
│ ├── netbird-watcher.service
│ └── README.md
├── README.md
└── PAIN_POINTS.md
Deployment
Prerequisites
- VPS with Docker
- DNS records pointing to VPS
- Ansible installed locally
- Terraform installed locally
1. Deploy Core Infrastructure
# NetBird
cd ansible/netbird
./generate-vault.sh
ansible-vault encrypt group_vars/vault.yml
ansible-playbook -i poc-inventory.yml playbook-ssl.yml --ask-vault-pass
# Gitea
cd ../gitea
ansible-playbook -i poc-inventory.yml playbook.yml
# Caddy (reverse proxy)
cd ../caddy
ansible-playbook -i poc-inventory.yml playbook.yml
# Gitea Runner
cd ../gitea-runner
ansible-playbook -i poc-inventory.yml playbook.yml -e vault_gitea_runner_token=<TOKEN>
2. Deploy Watcher Service
cd ansible/netbird-watcher
ansible-playbook -i poc-inventory.yml playbook.yml -e vault_netbird_token=<TOKEN>
3. Initialize Terraform
cd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with NetBird PAT
terraform init
terraform apply
4. Configure Gitea
Push terraform directory to Gitea repo, configure secret NETBIRD_TOKEN.
Adding a New Operator
-
Add setup key to
terraform/setup_keys.tf:resource "netbird_setup_key" "pilot_ivanov" { name = "pilot-ivanov" type = "one-off" auto_groups = [netbird_group.pilots.id] usage_limit = 1 ephemeral = false } output "pilot_ivanov_key" { value = netbird_setup_key.pilot_ivanov.key sensitive = true } -
Commit, push, merge PR
-
Retrieve key:
terraform output -raw pilot_ivanov_key -
Send key to operator
-
Operator enrolls -> watcher auto-renames peer
Monitoring
Watcher Service
# Status
systemctl status netbird-watcher
# Logs
journalctl -u netbird-watcher -f
# Processed keys
cat /var/lib/netbird-watcher/state.json
Cleanup
# Destroy Terraform resources
cd terraform && terraform destroy
# Stop services on VPS
ssh root@observability-poc.networkmonitor.cc
systemctl stop netbird-watcher
cd /opt/caddy && docker compose down
cd /opt/gitea && docker compose down
cd /opt/netbird && docker compose down
Description
Languages
Jinja
65.9%
Python
22.7%
HCL
6.6%
Shell
4.8%