4085c4fecd3455d35f48c2831646f98098cf62d9
All checks were successful
Terraform / terraform (push) Successful in 6s
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: Core functionality working, remaining pain points documented
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
Remaining Pain Points
See PAIN_POINTS.md for detailed analysis of:
- Peer naming automation (no link between setup keys and enrolled peers)
- Per-user vs per-role setup keys
- Secure key distribution to operators
Architecture
+-------------------+ PR/Merge +-------------------+
| Engineer | ----------------> | Gitea |
| (edits .tf) | | (gitea-poc.*) |
+-------------------+ +-------------------+
|
| CI/CD
v
+-------------------+
| Terraform |
| (in Actions) |
+-------------------+
|
| API calls
v
+-------------------+ Enroll +-------------------+
| Operators | ----------------> | NetBird |
| (use setup keys) | | (netbird-poc.*) |
+-------------------+ +-------------------+
Directory Structure
netbird-gitops-poc/
├── ansible/ # Deployment playbooks
│ ├── caddy/ # Shared reverse proxy
│ ├── gitea/ # Standalone Gitea (no OAuth)
│ ├── gitea-runner/ # Gitea Actions runner
│ └── netbird/ # NetBird with embedded IdP
├── terraform/ # Terraform configuration (Gitea repo content)
│ ├── .gitea/workflows/ # CI/CD workflow
│ │ └── terraform.yml
│ ├── main.tf # Provider config
│ ├── variables.tf # Input variables
│ ├── groups.tf # Group resources
│ ├── policies.tf # Policy resources
│ ├── setup_keys.tf # Setup key resources
│ ├── outputs.tf # Output values
│ ├── terraform.tfstate # State (committed for POC)
│ ├── terraform.tfvars # Secrets (gitignored)
│ └── terraform.tfvars.example
├── README.md
└── PAIN_POINTS.md
Quick Start
Prerequisites
- VPS with Docker
- DNS records pointing to VPS
- Ansible installed locally
- Terraform installed locally (for initial setup)
1. Deploy Infrastructure
# 1. NetBird (generates secrets, needs vault password)
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
# 2. Gitea
cd ../gitea
ansible-playbook -i poc-inventory.yml playbook.yml
# 3. Caddy (reverse proxy for both)
cd ../caddy
ansible-playbook -i poc-inventory.yml playbook.yml
# 4. Gitea Runner (get token from Gitea Admin -> Actions -> Runners)
cd ../gitea-runner
ansible-playbook -i poc-inventory.yml playbook.yml -e vault_gitea_runner_token=<TOKEN>
2. Initial Terraform Setup (Local)
cd terraform
# Create tfvars with your NetBird PAT
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with actual token
# Initialize and apply
terraform init
terraform apply
3. Push to Gitea
cd terraform
git init
git add .
git commit -m "Initial Terraform config"
git remote add origin git@gitea-poc.networkmonitor.cc:admin/netbird-iac.git
git push -u origin main
4. Configure Gitea Secrets
In Gitea repository Settings -> Actions -> Secrets:
NETBIRD_TOKEN: Your NetBird PAT
5. Make Changes via GitOps
Edit Terraform files locally, push to create PR:
# groups.tf - add a new group
resource "netbird_group" "new_team" {
name = "new-team"
}
git checkout -b add-new-team
git add groups.tf
git commit -m "Add new-team group"
git push -u origin add-new-team
# Create PR in Gitea -> CI runs terraform plan
# Merge PR -> CI runs terraform apply
CI/CD Workflow
The .gitea/workflows/terraform.yml workflow:
| Event | Action |
|---|---|
| Pull Request | terraform plan (preview changes) |
| Push to main | terraform apply (apply changes) |
| After apply | Commit updated state file |
State Management: State is committed to git (acceptable for single-operator POC). For production, use a remote backend.
Key Discoveries
NetBird API Behavior
- Peer IDs are not predictable - Generated server-side at enrollment time
- No setup key -> peer link - NetBird doesn't record which setup key enrolled a peer
- Peers self-enroll - Cannot create peers via API (WireGuard keypair generated locally)
- Terraform URL format - Use
https://domain.comNOThttps://domain.com/api
Credentials Reference (POC Only)
| Service | Credential | Location |
|---|---|---|
| NetBird PAT | nbp_T3yD... |
Dashboard -> Team -> Service Users |
| Gitea | admin user | Created during setup |
| VPS | root | observability-poc.networkmonitor.cc |
Warning: Rotate all credentials before any production use.
Cleanup
# Destroy Terraform resources
cd terraform
terraform destroy
# Stop VPS services
ssh root@observability-poc.networkmonitor.cc
cd /opt/caddy && docker compose down
cd /opt/gitea && docker compose down
cd /opt/netbird && docker compose down
Next Steps
See PAIN_POINTS.md for remaining challenges to address before production use.
Description
Languages
Jinja
65.9%
Python
22.7%
HCL
6.6%
Shell
4.8%