NetBird Deployment
Self-hosted NetBird VPN with embedded IdP (no external SSO required).
Quick Start
cd ansible/deployments/netbird
# 1. Generate secrets
./generate-vault.sh
ansible-vault encrypt group_vars/vault.yml
# 2. Deploy
ansible-playbook playbook-ssl.yml -i inventory.yml --ask-vault-pass
# 3. Create admin (manual - see below)
# 4. Create PAT (manual - see below)
# 5. Provision groups and users
ansible-playbook setup-groups.yml -i inventory.yml --ask-vault-pass
ansible-playbook setup-users.yml -i inventory.yml --ask-vault-pass
Prerequisites
- Ubuntu 22.04+ VPS with public IP
- Ports 80, 443, 3478 (TCP/UDP) open
- Ansible 2.14+
Deployment Modes
| Playbook | Use Case |
|---|---|
playbook-ssl-ip.yml |
HTTPS with self-signed cert on IP (recommended for isolated deployments) |
playbook-ssl.yml |
HTTPS with Let's Encrypt (requires domain) |
playbook-no-ssl.yml |
HTTP only (not recommended) |
Full Workflow
Step 1: Configure Inventory
Edit inventory.yml:
all:
children:
netbird_servers:
hosts:
netbird-vps:
ansible_host: YOUR_SERVER_IP
ansible_user: root
ansible_python_interpreter: /usr/bin/python3
Step 2: Generate Secrets
./generate-vault.sh
ansible-vault encrypt group_vars/vault.yml
This creates:
vault_turn_password- TURN server authenticationvault_relay_secret- Relay server secretvault_encryption_key- Embedded IdP encryption (BACK THIS UP!)vault_admin_password- Initial admin passwordvault_netbird_service_pat- Leave empty for now
Step 3: Deploy NetBird
ansible-playbook playbook-ssl-ip.yml -i inventory.yml --ask-vault-pass
Wait for deployment to complete. Dashboard will be available at https://YOUR_IP.
Step 4: Create Admin User (Manual)
- Open
https://YOUR_IPin browser (accept certificate warning) - Create admin account:
- Email:
admin@achilles.local(fromgroup_vars/netbird_servers.yml) - Password: Use
vault_admin_passwordfrom vault
- Email:
# View password
ansible-vault view group_vars/vault.yml | grep vault_admin_password
Step 5: Create Service User & PAT (Manual)
After logging in as admin:
- Go to Team → Service Users
- Click Create Service User
- Name:
Automation Service - Role:
Admin
- Name:
- Click on the created service user
- Click Create Token
- Name:
ansible-automation - Expiration: 365 days
- Name:
- Copy the token (shown only once!)
Step 6: Store PAT in Vault
ansible-vault edit group_vars/vault.yml
Set:
vault_netbird_service_pat: "nbp_xxxxxxxxxxxxxxxxxxxx"
Step 7: Create Groups
ansible-playbook setup-groups.yml -i inventory.yml --ask-vault-pass
This creates:
- Battalion groups:
battalion-1-pilots,battalion-1-ground-stations, etc. - Dev team group:
dev-team - Setup keys for each group
- Access policies (battalion isolation + dev access)
Step 8: Provision Users
Edit group_vars/netbird_servers.yml to define users:
netbird_users:
# Dev team (full access)
- email: "vlad.stus@achilles.local"
name: "Vlad Stus"
role: "admin"
auto_groups:
- "dev-team"
# Battalion users
- email: "pilot1.bat1@achilles.local"
name: "Pilot One B1"
role: "user"
battalion: "battalion-1"
type: "pilot"
- email: "gs-operator1.bat1@achilles.local"
name: "GS Operator One B1"
role: "user"
battalion: "battalion-1"
type: "ground-station"
Then run:
ansible-playbook setup-users.yml -i inventory.yml --ask-vault-pass
Dry run (preview without creating):
ansible-playbook setup-users.yml -i inventory.yml --ask-vault-pass -e "dry_run=true"
Credentials are saved to files/credentials/users-YYYY-MM-DD.yml.
User Roles
| Role | Permissions |
|---|---|
owner |
Full control, can delete account |
admin |
Manage users, groups, policies, setup keys |
user |
Connect peers, view own peers |
User Types (for battalion assignment)
| Type | Auto-Group |
|---|---|
pilot |
{battalion}-pilots |
ground-station |
{battalion}-ground-stations |
Backup & Restore
Create backup (downloads to ~/achilles-backups/netbird/):
ansible-playbook backup.yml -i inventory.yml
Restore latest backup:
ansible-playbook restore.yml -i inventory.yml
Restore specific backup:
ansible-playbook restore.yml -i inventory.yml -e "backup_file=netbird-backup-20250116T120000.tar.gz"
Backups include:
- Management SQLite database (peers, routes, policies, users)
- Configuration files (docker-compose.yml, Caddyfile, etc.)
Cleanup
Soft cleanup (stop containers, keep data):
ansible-playbook cleanup-soft.yml -i inventory.yml
Full cleanup (remove everything including data):
ansible-playbook cleanup-full.yml -i inventory.yml
Connecting Peers
After provisioning, users connect with:
# Using setup key (for automated deployments)
netbird up --management-url https://YOUR_IP --setup-key SETUP_KEY
# Using user credentials (interactive)
netbird up --management-url https://YOUR_IP
# Then login with email/password in browser
File Structure
ansible/deployments/netbird/
├── backup.yml # Backup management DB and config
├── cleanup-full.yml # Remove everything
├── cleanup-soft.yml # Stop containers only
├── generate-vault.sh # Generate random secrets
├── group_vars/
│ ├── netbird_servers.yml # Main configuration
│ ├── vault.yml # Encrypted secrets
│ └── vault.yml.example # Template for vault
├── inventory.yml # Server inventory
├── playbook-no-ssl.yml # HTTP deployment
├── playbook-ssl-ip.yml # HTTPS with self-signed (IP)
├── playbook-ssl.yml # HTTPS with Let's Encrypt
├── restore.yml # Restore from backup
├── setup-bootstrap.yml # Bootstrap admin (if API available)
├── setup-groups.yml # Create groups, keys, policies
├── setup-users.yml # Provision users
├── templates/ # Jinja2 templates
└── files/
└── credentials/ # Generated user passwords (gitignored)
Troubleshooting
Certificate Warning
Expected for SSL-IP mode. Accept the self-signed certificate in your browser.
"Unauthenticated" after login
JWKS race condition bug in NetBird. Wait 30 seconds and try again, or restart the management container:
ssh root@YOUR_IP "cd /opt/netbird && docker compose restart management"
API returns 404 on /api/instance/setup
The bootstrap API isn't exposed in all NetBird versions. Create admin manually in the dashboard.
View logs
ssh root@YOUR_IP "cd /opt/netbird && docker compose logs -f"
Check container status
ssh root@YOUR_IP "cd /opt/netbird && docker compose ps"