Cloudron app package for NetBird -- a self-hosted WireGuard mesh VPN with SSO, MFA, and granular access controls.
NetBird connects devices into a secure peer-to-peer WireGuard mesh network. The entire control plane is open-source and self-hostable.
| Feature | Details |
|---|---|
| Protocol | WireGuard (kernel or userspace) |
| Architecture | Peer-to-peer mesh (no hub-and-spoke) |
| NAT traversal | ICE + STUN + built-in relay fallback |
| Auth | Embedded IdP + any OIDC provider (Cloudron SSO, Keycloak, Google, Entra ID, etc.) |
| Access control | Group-based policies with posture checks |
| API | Full REST API + Terraform provider |
| License | BSD-3 (client), AGPL-3.0 (server) |
This app packages the NetBird combined server. See CloudronManifest.json
for the pinned upstream version. It includes:
- Management API -- peer registry, ACLs, setup keys, network routes
- Signal server -- WebRTC ICE negotiation for P2P connection setup
- Relay server -- fallback for peers behind strict NAT
- STUN -- NAT discovery through Cloudron's platform STUN service by default, or NetBird's own STUN server on an optional dedicated UDP port
- Embedded IdP -- built-in user management (Dex) with
/setuponboarding page - Dashboard -- web UI for administration
- Optional Reverse Proxy -- public HTTPS services through mesh peers, using the single-VPS second-IP setup. Disabled by default.
NetBird clients connect to this server to join the mesh.
For multiple brands, see multi-instance security. Private meshes can share the primary IP with unique ports; the optional public proxy host helper currently supports only one ingress instance per host.
See the documentation index and Cloudron integration boundaries. SSO, app entitlement, mesh authorization and Cloudron VPN protection are separate controls.
The opt-in access reconciler uses Cloudron's effective user/group permissions for the configured SSO provider. It is an operator tool; an app update does not enable background synchronization or migrate existing peers.
| Resource | Minimum |
|---|---|
| Cloudron | v9.1.0+ |
| Memory | 512 MB (configurable in manifest) |
| Ports | TCP 443 plus configurable TCP 33073; Cloudron STUN on UDP 3478 |
Important: Keep the selected native TCP port accessible from clients. Cloudron maps it to container port 33074.
STUN uses Cloudron's platform STUN/TURN service by default (UDP 3478, already open on Cloudron). Clients only send unauthenticated STUN Binding requests to it; Cloudron's TURN credentials are not used. NetBird's built-in relay still handles peers that cannot connect directly. To run NetBird's own STUN server instead, enable the optional dedicated STUN port (default 3479) in the app's Location settings. Existing installations keep their STUN port binding, and therefore their embedded STUN server, until you disable it.
# Install Cloudron CLI
npm install -g cloudron
# Login to your Cloudron instance
cloudron login my.cloudron.example
# Clone and build
git clone https://github.com/marcusquinn/cloudron-netbird-app.git
cd cloudron-netbird-app
cloudron build
# Install
cloudron install --location netbirdNot yet available. See Contributing if you'd like to help get it there.
- Open
https://netbird.your-cloudron.examplein your browser - You'll be redirected to the setup page (
/setup) - Create your admin account (email + password)
- Log in with the credentials you just created
- Navigate to Setup Keys to create a key for connecting devices
- Install the NetBird client on your devices and connect:
# Install client
curl -fsSL https://pkgs.netbird.io/install.sh | sh
# Connect to your self-hosted management server.
# Port 33073 is the default; replace it if Cloudron assigned another TCP port.
sudo netbird up \
--setup-key YOUR_SETUP_KEY \
--management-url https://netbird.your-cloudron.example:33073 \
--enable-rosenpass --rosenpass-permissiveThe Rosenpass flags add post-quantum keys to tunnels between devices that have
it and still connect to devices that don't. The dashboard's Add Peer
command includes them by default; see
post-quantum protection for
strict mode and the NETBIRD_CLIENT_ROSENPASS setting.
The native client port accepts only post-quantum TLS by default, so clients
must be NetBird 0.61.0 or later.
The /setup page is only accessible when no users exist. After creating the first user, it redirects to the regular login page.
Cloudron's addon remains an install-time opt-in with optionalSso: true and
--no-sso. With SSO ticked at install, creating the owner at /setup adds
Cloudron as a login option automatically, with new-user approval required
(details).
Existing installs can switch Cloudron login on/off through the supported custom
OIDC client API, without changing their install-time flag or migrating peers.
See SSO switching and recovery. Updates preserve embedded
login; nothing is registered at startup.
The manual steps below apply when automatic registration reported a failure, or when adding Cloudron to an install whose owner already exists.
-
Choose one onboarding path: the optional addon instructions below for a new install, or the managed SSO switch for another app. Do not register two connectors for the same integration. No reinstall is required for the managed path on a platform supporting custom OIDC clients.
-
Open NetBird, complete
/setup, and verify the embedded owner can sign out and back in. Keep that owner as the emergency login. -
Log in as the embedded owner and open Settings > Identity Providers > Add Identity Provider. Select Generic OIDC.
-
In a trusted administrator terminal, read the three addon values separately:
cloudron exec --app netbird -- printenv CLOUDRON_OIDC_ISSUER cloudron exec --app netbird -- printenv CLOUDRON_OIDC_CLIENT_ID cloudron exec --app netbird -- printenv CLOUDRON_OIDC_CLIENT_SECRET
Treat the final value as a secret: do not paste it into tickets, logs, or screenshots.
-
Enter Name
Cloudron, the issuer, client ID, and client secret. The Cloudron addon is registered for NetBird's exact callbackhttps://<app-domain>/oauth2/callbackand logout callbackhttps://<app-domain>/oauth2/logout/callback. -
Save the provider. In a private browser window, confirm the Cloudron button completes login before announcing SSO to users. Then sign back in with the embedded owner to prove fallback access still works.
NetBird requires an authenticated owner to create the connector. This package never saves an owner token or registers at startup: doing so could overwrite a differently configured provider, expose the client secret, or silently change identity ownership. The automatic path uses the owner's own setup request and a token deleted straight after.
- A Cloudron identity and an embedded identity are distinct even when their email addresses match. NetBird does not merge them or promote the external identity to owner. Review the new user in Team > Users and grant only the intended role; new external users can require owner approval.
- Keep Continue with Email tested. An unavailable Cloudron IdP must not remove embedded owner access or affect setup-key peer enrolment.
- For the managed integration, use its disable operation to retain external identity mapping. Do not delete/recreate an identity-bearing connector as a casual toggle. Retiring a manually configured connector requires a separate external-user/session plan; keep embedded owner recovery tested.
- Multiple external providers can coexist. Enable JWT group sync only after
validating Cloudron's
groupsclaim and an explicit allow-group policy.
Cloudron Server
+----------------------------------------------------------+
| Cloudron HTTPS :443 -> nginx :8080 |
| dashboard, REST API, embedded IdP |
| |
| selected TCP port -> nginx :33074 (Cloudron TLS cert) |
| native HTTP/2 gRPC + relay/WebSocket |
| | |
| v |
| netbird-server :80 |
| Management + Signal + Relay + embedded IdP |
| |
| TURN addon -> Cloudron STUN advertised to clients |
| (optional UDP port -> embedded STUN instead) |
| PostgreSQL addon -> persistent application data |
+----------------------------------------------------------+
All configuration is generated at runtime by start.sh. There are no config files to edit manually -- the app reads Cloudron environment variables, writes config.yaml and nginx.conf, and substitutes those values into an ephemeral dashboard export on each start.
Uses Cloudron's PostgreSQL addon automatically. No manual database setup required.
Built-in: NetBird's embedded IdP (Dex) handles initial setup and local user management. The /setup page creates the first admin account.
Optional: Cloudron SSO or any OIDC provider can be added via the dashboard after initial setup. Multiple providers can coexist. See Adding Cloudron SSO.
The manifest uses /oauth2/.well-known/openid-configuration as the health check
endpoint, served through the internal nginx proxy on port 8080. The combined
server also exposes a health endpoint on port 9000, which is not routed through
nginx.
The native client port accepts only TLS 1.3 with the hybrid X25519MLKEM768
key exchange. Set NETBIRD_TLS_ALLOW_CLASSIC=true with cloudron env set to
also allow classic key exchange for clients older than NetBird 0.61.0. See
post-quantum protection for port 443 and Rosenpass.
Cloudron's health check does not cover the native client port or the optional proxy ingress. We recommend updown.io (referral link) with the check plan in external monitoring.
All persistent data is stored in /app/data/ (Cloudron's localstorage addon) and included in Cloudron backups:
| Path | Contents |
|---|---|
/app/data/config/config.yaml |
Combined server configuration (regenerated on each start) |
/app/data/config/nginx.conf |
Internal nginx configuration (regenerated on each start) |
/app/data/config/.encryption_key |
Database encryption key (generated on first run, persisted) |
/app/data/config/.auth_secret |
Relay authentication secret (generated on first run, persisted) |
/app/data/netbird/ |
Server state and data |
/app/data/.initialized |
First-run marker file |
The encryption key encrypts setup keys and API tokens at rest in PostgreSQL. Both .encryption_key and .auth_secret are included in Cloudron backups. Do not lose them -- losing the encryption key means regenerating all setup keys and API tokens, while losing the auth secret may disrupt relay server authentication.
# Build and install
cloudron build
cloudron install --location netbird
# Iterate after changes
cloudron build && cloudron update --app netbird
# View logs
cloudron logs -f --app netbird
# Shell into the container
cloudron exec --app netbird
# Debug mode (writable filesystem)
cloudron debug --app netbird
# Uninstall
cloudron uninstall --app netbirdThe optional smoke check requires Python 3 and a running Docker engine. Build the linux/amd64 candidate and pull the pinned PostgreSQL 16 fixture first:
docker build --platform linux/amd64 -t netbird-smoke:candidate .
docker pull postgres@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20
python3 test/runtime-smoke.py --image netbird-smoke:candidate
python3 test/runtime-smoke.py --image netbird-smoke:candidate --cloudron-sso-fixtureThe runner never pulls a candidate automatically. It uses disposable labeled
containers, a network, application-data and certificate fixture volumes, with no
published host ports or TTY. Runtime containers are limited to 512 MB and two
CPUs. The app has a read-only root filesystem and writable /run, /tmp, and
/app/data.
Outbound access is needed for NetBird's geolocation database download.
Fresh startup and restart must pass the manifest health check, setup/config
requests, dedicated TLS listener and HTTP/2 negotiation checks, stable
parent/child UID checks, database initialization, and persisted secret/data
checks. The optional SSO fixture supplies an unreachable, synthetic OIDC
environment and proves that its client secret is not written to generated
configuration or dashboard assets while embedded setup/restart still pass. Raw logs,
credentials, and secret fingerprints are withheld.
The default test deadline is 180 seconds (--timeout adjusts it), plus up to 60
seconds for ownership-checked cleanup on success, failure, or SIGINT/SIGTERM.
Cleanup failures print the exact owned resource names and label to inspect;
remove only those resources after confirming that label. SIGKILL or a stopped
Docker daemon can prevent cleanup. Never use a broad Docker prune as recovery.
This is not a live Cloudron, backup/restore, VPN-client, or successful external
SSO login test. The fast
bash test/package-test.sh remains independent of Docker. The only supported
runtime target is the local Docker fixture; it never discovers a remote host or
defaults to a production instance. See
test/QUALIFICATION.md for the evidence-indexed
qualification matrix, maintenance consent boundaries, and open live-instance
cases.
- Fresh install completes without errors
- Dashboard loads at app URL
-
/setuppage appears on first run (no users exist) - Admin account creation works via setup page
- Login with created credentials works
- Setup key creation works in dashboard
- Client connects with a setup key and selected TCP management port
- Peers can ping each other through the mesh
- Peers behind NAT connect via relay
- Native HTTP/2 gRPC works on the dedicated port (signal + management)
- WebSocket connections work (relay + ws-proxy)
- App survives restart (
cloudron restart --app netbird) - Backup/restore preserves all state
- Memory stays within 512 MB limit
- Clients receive a reachable STUN server (Cloudron STUN, or the optional dedicated STUN UDP port when enabled)
- (Optional) Cloudron SSO callback and login work on isolated staging
- A denied/pending external user receives no unintended access or owner role
- Embedded-owner login and setup-key enrolment work during IdP outage
- Deleting only the Cloudron connector restores the pre-SSO login choices
The pinned supervisor health issue reports repository-development activity; it
is not the deployed NetBird application's health check. Its refresh can pause
while the repository has no active pull requests, assigned issues,
auto-dispatch work, or workers. Before treating an old last_refresh: value as
a scheduler or application failure, confirm that the stats launch agent is
loaded and that recent stats-log entries end with a successful health-issue
update.
cloudron-netbird-app/
CloudronManifest.json # Cloudron app metadata and addon requirements
Dockerfile # Build instructions (copies netbird-server + dashboard)
start.sh # Runtime entry point (config generation, process launch)
supervisord.conf # Process management (nginx + netbird-server)
logo.png # App icon (256x256)
AGENTS.md # AI entrypoint and critical package contracts
docs/ # Architecture, operator and publishing guides
.agents/ # AI-specific working instructions
CHANGELOG.md # Version history
CONTRIBUTING.md # Contribution guidelines
LICENSE # MIT
.editorconfig # Editor formatting rules
- Native client port: Clients must include the selected TCP port in
--management-url. The normal app URL remains the dashboard and REST API. Opening the selected native port in a browser redirects GET/HEAD dashboard navigation to that canonical HTTPS URL; native transport endpoints remain on the selected port. - Cloudron TURN is STUN-only here: Cloudron's TURN relay issues time-limited credentials from a shared secret, which NetBird's static external TURN entries cannot generate. The package therefore advertises only Cloudron's STUN service and keeps NetBird's built-in relay for fallback.
- Reverse Proxy needs separate ingress: Cloudron's normal HTTPS endpoint cannot provide raw TLS passthrough. The optional second-IP host bridge supports HTTPS services on one VPS without modifying platform nginx. It is administrator-managed host configuration, not an automatic Cloudron feature.
- Single account mode: All users join the same network. This suits most self-hosted deployments.
- Qualification scope: Native peer registration and public HTTPS-to-peer proxying have passed on a live Cloudron installation. One maintenance run covered host reboots, a Cloudron upgrade and an Ubuntu 26.04 release upgrade. Restore, Docker-only restart, post-reboot peer tunnels and end-to-end proxied HTTP after a reboot remain separate maintenance-window tests.
- NetBird: https://github.com/netbirdio/netbird
- NetBird Docs: https://docs.netbird.io
- NetBird Self-Hosting: https://docs.netbird.io/selfhosted/selfhosted-quickstart
- NetBird Configuration: https://docs.netbird.io/selfhosted/configuration-files
- NetBird External Reverse Proxy: https://docs.netbird.io/selfhosted/external-reverse-proxy
Contributions are welcome. The main areas that need work:
- Testing on a real Cloudron instance -- the packaging needs real-world validation
- Auth flow testing -- verify the embedded IdP setup page and login work end-to-end
- gRPC/WebSocket testing -- verify signal, management, and relay on the dedicated TLS port
- App Store submission -- once tested, submit to the Cloudron App Store
- Test thoroughly on a real Cloudron instance
- Post on the Cloudron forum packaging category to request a project on git.cloudron.io
- The Cloudron team creates a repo under
git.cloudron.io/packages/netbird-app - Push the package and submit for review
Note: git.cloudron.io does not allow personal project creation. The Cloudron team manages the packages/ namespace.
This Cloudron app package is licensed under the MIT License.
NetBird itself is licensed under BSD-3 (client) and AGPL-3.0 (server). See the NetBird repository for details.