Skip to content

docs: add Ansible IaC guide for tenant configuration#759

Open
SunsetDrifter wants to merge 1 commit into
mainfrom
docs/ansible-iac
Open

docs: add Ansible IaC guide for tenant configuration#759
SunsetDrifter wants to merge 1 commit into
mainfrom
docs/ansible-iac

Conversation

@SunsetDrifter

@SunsetDrifter SunsetDrifter commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New page Configure NetBird with Ansible at /selfhosted/iac/ansible, documenting the community.ansible_netbird collection for managing tenant resources (users, groups, setup keys, policies, networks, DNS, posture checks, IdPs) declaratively against the REST API.
  • New sidebar group Infrastructure as Code under Self-Host NetBird, sized to accept additional IaC tools later (e.g. Terraform).
  • Cross-link from selfhosted/automated-setup.mdx so operators who just minted their first PAT see the declarative next step.

Scope notes

  • Covers tenant configuration only. Fleet-installing the NetBird client on machines via Ansible is intentionally out of scope; that use case is already mentioned in manage/peers/register-machines-using-setup-keys.mdx.
  • The collection works against any NetBird tenant (cloud or self-hosted); the page calls that out explicitly so the placement under Self-Host doesn't mislead cloud operators.
  • Links out to upstream examples/ instead of mirroring playbooks that would rot.

Summary by CodeRabbit

  • New Features

    • Added a new documentation page for managing NetBird with Ansible, including setup, authentication, examples, supported resource types, and troubleshooting.
    • Added a new “Infrastructure as Code” entry under the self-hosted documentation navigation, linking to the Ansible guide.
  • Documentation

    • Updated the automated setup guide to reference declarative bootstrap tasks and point to the Ansible instructions.

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new documentation page for managing NetBird resources declaratively via the community.ansible_netbird Ansible collection, including prerequisites, authentication, an example playbook, module reference, and troubleshooting. Wires the page into site navigation and cross-links it from the automated-setup guide.

Changes

Ansible Documentation

Layer / File(s) Summary
Navigation and cross-link wiring
src/components/NavigationDocs.jsx, src/pages/selfhosted/automated-setup.mdx
Adds an "Infrastructure as Code" nav group linking to the Ansible page and a sentence linking the automated-setup guide to it.
Overview, prerequisites, and setup
src/pages/selfhosted/iac/ansible.mdx
Adds page description, overview of REST API management, prerequisites, and build/install instructions for the collection.
Authentication and example playbook
src/pages/selfhosted/iac/ansible.mdx
Documents environment variable/module_defaults authentication and a full example playbook creating a group and setup key with secret persistence.
Module reference and troubleshooting
src/pages/selfhosted/iac/ansible.mdx
Lists supported resource modules and adds troubleshooting guidance for auth errors, TLS, timeouts, and setup key behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: braginini

Poem

A hop, a skip, a doc so bright,
Ansible pages coming into light,
Groups and keys and setup right,
Nav links guide the reader's flight,
This bunny thumps with pure delight! 🐇📜

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an Ansible IaC guide for tenant configuration docs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/ansible-iac

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/components/NavigationDocs.jsx

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from:
at JSON.stringify ()
at file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:308:45
at Array.map ()
at ConfigValidator.formatErrors (file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:299:23)
at ConfigValidator.validateConfigSchema (file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:330:84)
at ConfigArrayFactory._normalizeConfigData (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:676:19)
at ConfigArrayFactory._loadConfigData (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:641:21)
at ConfigArrayFactory._loadExtendedShareableConfig (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:946:21)
at ConfigArrayFactory._loadExtends (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:814:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:752:25)

src/pages/selfhosted/automated-setup.mdx

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from:
at JSON.stringify ()
at file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:308:45
at Array.map ()
at ConfigValidator.formatErrors (file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:299:23)
at ConfigValidator.validateConfigSchema (file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:330:84)
at ConfigArrayFactory._normalizeConfigData (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:676:19)
at ConfigArrayFactory._loadConfigData (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:641:21)
at ConfigArrayFactory._loadExtendedShareableConfig (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:946:21)
at ConfigArrayFactory._loadExtends (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:814:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:752:25)

src/pages/selfhosted/iac/ansible.mdx

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from:
at JSON.stringify ()
at file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:308:45
at Array.map ()
at ConfigValidator.formatErrors (file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:299:23)
at ConfigValidator.validateConfigSchema (file:///node_modules/@eslint/eslintrc/lib/shared/config-validator.js:330:84)
at ConfigArrayFactory._normalizeConfigData (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:676:19)
at ConfigArrayFactory._loadConfigData (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:641:21)
at ConfigArrayFactory._loadExtendedShareableConfig (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:946:21)
at ConfigArrayFactory._loadExtends (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:814:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (file:///node_modules/@eslint/eslintrc/lib/config-array-factory.js:752:25)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SunsetDrifter SunsetDrifter force-pushed the docs/ansible-iac branch 8 times, most recently from 8d94e74 to be642f9 Compare July 8, 2026 15:28
Document the community.ansible_netbird collection for managing NetBird
resources (users, groups, setup keys, policies, networks, DNS, posture
checks) declaratively via the REST API. Adds a new Infrastructure as
Code subsection under Self-Host NetBird with room for future entries.

Cross-links from the Automated Setup PAT bootstrap page since the
collection is the natural next step after obtaining the first token.
@SunsetDrifter SunsetDrifter marked this pull request as ready for review July 9, 2026 06:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/selfhosted/iac/ansible.mdx`:
- Around line 1-160: The docs page currently links the NetBird REST API to a
stale internal path, so update the reference in the description/introduction
content to the existing public API docs target instead. Fix the markdown link
that points to the API introduction from the `description` export or nearby
prose in `ansible.mdx`, and use the public API page path already used elsewhere
in the site (or add a redirect if that’s the chosen docs pattern).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 87a1a4f8-6806-4cab-a0b0-782d50c7e7b0

📥 Commits

Reviewing files that changed from the base of the PR and between 25898dc and 7c86bd0.

📒 Files selected for processing (3)
  • src/components/NavigationDocs.jsx
  • src/pages/selfhosted/automated-setup.mdx
  • src/pages/selfhosted/iac/ansible.mdx

Comment on lines +1 to +160
export const description = 'Manage NetBird users, groups, peers, setup keys, policies, networks, DNS, and posture checks declaratively with the community.ansible_netbird collection.'

# Configure NetBird with Ansible

The [`community.ansible_netbird`](https://github.com/netbirdio/ansible-netbird) collection manages NetBird resources — users, groups, peers, setup keys, policies, networks, DNS, posture checks, and identity providers — declaratively against the [NetBird REST API](/api/introduction). Use it to define your tenant configuration in version control and reapply it from CI.

The collection talks to the API of an existing NetBird instance. It does not install the NetBird client on machines and does not deploy the self-hosted server. It works against any NetBird tenant — cloud or self-hosted — that you can reach with a Personal Access Token.

## Requirements

- Ansible 2.15 or newer.
- Python 3.9 or newer on the Ansible control node.
- The Management API URL for your NetBird instance, for example `https://netbird.example.com`.
- A Personal Access Token (PAT) for a NetBird admin or service user.

For a brand-new self-hosted instance with no users yet, see [Automated setup with a Personal Access Token](/selfhosted/automated-setup) to obtain the first PAT.

## Install the collection

The collection is not yet published to Ansible Galaxy. Build and install it from source:

```bash
git clone https://github.com/netbirdio/ansible-netbird.git
cd ansible-netbird
ansible-galaxy collection build
ansible-galaxy collection install community-ansible_netbird-*.tar.gz
```

## Authenticate

Modules accept credentials as parameters, environment variables, or role variables. Environment variables are the simplest for local runs and CI:

```bash
export NETBIRD_API_URL="https://netbird.example.com"
export NETBIRD_API_TOKEN="nbp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

<Warning>
Set `api_url` to the base URL of your NetBird instance — do not include `/api`. The collection appends API paths automatically.
</Warning>

<Note>
Store the PAT in Ansible Vault, your CI's secret store, or an environment variable — never commit it to source control.
</Note>

When your credentials come from Ansible Vault or group variables rather than the environment, set them once for every NetBird module with `module_defaults` instead of repeating them on each task:

```yaml
module_defaults:
group/community.ansible_netbird.netbird:
api_url: "{{ netbird_api_url }}"
api_token: "{{ netbird_api_token }}"
```

The collection defines the `community.ansible_netbird.netbird` action group, so these defaults apply to every module. On ansible-core 2.15, place `module_defaults` at the block level rather than the play level to avoid a variable-resolution timing issue when the values come from group variables.

## First playbook

This playbook creates a group and a reusable setup key bound to that group. Save it as `netbird.yml`:

```yaml {{ title: 'netbird.yml' }}
- name: Configure NetBird tenant
hosts: localhost
gather_facts: false
tasks:
- name: Create a group for servers
community.ansible_netbird.netbird_group:
name: servers
state: present
register: servers_group

- name: Create a reusable setup key for servers
community.ansible_netbird.netbird_setup_key:
name: server-enrollment
key_type: reusable
expires_in: 604800
auto_groups:
- "{{ servers_group.group.id }}"
state: present
register: setup_key

- name: Save the setup key (its secret is returned only on creation)
ansible.builtin.copy:
content: "{{ setup_key.setup_key.key }}"
dest: ./server-enrollment.key
mode: "0600"
no_log: true
when: setup_key.setup_key.key is defined
```

`auto_groups` takes group IDs, not names, so the setup-key task references the group created in the first task through `servers_group.group.id`.

Run it against your tenant:

```bash
ansible-playbook netbird.yml
```

The setup key's secret is returned only the first time the key is created, so the playbook captures it then and writes it out with `0600` permissions. `no_log: true` keeps it out of the job log. Never print a setup key with `debug`, and store it in a secret manager for anything beyond a local test.

Re-running the playbook is safe — every module is idempotent and reports `changed` only when the API state differs from the playbook.

The [`examples/`](https://github.com/netbirdio/ansible-netbird/tree/main/examples) directory in the collection repository has fuller playbooks, from dynamic policies to a complete tenant setup.

## What you can manage

The collection ships a module for every resource type:

- **Users and service users** — `netbird_user`
- **User invites** — `netbird_invite` (onboard new users; create, delete, or regenerate)
- **Groups** — `netbird_group`
- **Setup keys** — `netbird_setup_key` (one-off or reusable, with auto-group assignment)
- **Peers** — `netbird_peer` (SSH, login and inactivity expiration, approval, name)
- **Policies** — `netbird_policy`
- **Posture checks** — `netbird_posture_check`
- **Networks with routers and resources** — `netbird_network` (the current routing model; replaces the deprecated routes API)
- **Routes** — `netbird_route` (the deprecated routes API; use `netbird_network` for new setups)
- **Reverse-proxy services** — `netbird_service` (publish a domain and forward it to one or more targets)
- **DNS settings and nameserver groups** — `netbird_dns`
- **DNS zones with records** — `netbird_dns_zone`
- **Identity providers** — `netbird_idp`
- **Personal access tokens** — `netbird_token`
- **Account settings** — `netbird_account`
- **Read any resource** — `netbird_info`

See these modules used together in the [`examples/`](https://github.com/netbirdio/ansible-netbird/tree/main/examples) directory of the collection repository.

## Troubleshooting

### Authentication fails with 401 or 403

- Confirm the PAT belongs to a user with admin permissions in the tenant.
- Confirm `NETBIRD_API_URL` points at the base URL without a trailing `/api` segment.
- Confirm the PAT has not expired or been revoked in the Dashboard.

### TLS verification fails

If your self-hosted instance uses a private or self-signed certificate authority, add the CA to the control node's system trust store, or point the `REQUESTS_CA_BUNDLE` or `SSL_CERT_FILE` environment variable at it. That is what the module's SSL error message tells you to do. Setting `validate_certs: false` also works, but it removes protection against on-path attacks, so keep it as a last resort.

### A warning says the API token is sent in cleartext

Your `api_url` uses `http://` instead of `https://`. The module still runs but warns, because the token travels unencrypted and is exposed to anyone on the network path. Switch to an `https://` URL, or put a TLS-terminating proxy in front of the Management API. This is a warning, not an error.

### A request is refused as a redirect

If a call fails saying the request was redirected and the module refused to follow it, your `api_url` is pointing somewhere that redirects, usually an `http://` URL that bounces to `https://`, or a base URL with an extra path. The module never follows redirects, so the API token is never forwarded to another host. Set `api_url` to the exact HTTPS base URL, for example `https://netbird.example.com` with no trailing path.

### Requests time out

Each API request times out after 30 seconds by default. On a slow link or a very large tenant, raise it with the `timeout` parameter (in seconds) on the module call, or set it once for every module through `module_defaults`.

### A setup key's secret is missing from the result

The key secret is returned by the API **only when the key is first created**. On an idempotent re-run, or for a key that already exists, there is no secret to return and `setup_key.setup_key.key` is undefined. Capture it the first time the key is created and store it in a secret manager. Ansible cannot mask an individual return field, so set `no_log: true` on any task that registers or handles the key rather than relying on it being hidden.

### Resources are not updated as expected

For modules like `netbird_user`, `netbird_group`, and `netbird_setup_key`, omitting a list field (for example `auto_groups` or `peers`) preserves the existing value rather than clearing it. To remove all members, pass an explicit empty list (`[]`).

A setup key is mostly fixed once created: only its `revoked` state and `auto_groups` change on later runs, so re-running with a different `key_type` or `expires_in` is silently ignored. Rotate the key to change those.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the internal link target exists as a page file
fd -t f 'introduction' src/pages/api/

# Check if the external GitHub repo is reachable
curl -sI -o /dev/null -w "%{http_code}" https://github.com/netbirdio/ansible-netbird

Repository: netbirdio/docs

Length of output: 257


🏁 Script executed:

#!/bin/bash
git ls-files | rg '(^|/)introduction\.(md|mdx)$|(^|/)api/.*/introduction\.(md|mdx)$|(^|/)api/introduction(\.(md|mdx))?$'
curl -sL -o /tmp/ansible-netbird.html -w "%{http_code}\n" https://github.com/netbirdio/ansible-netbird
python3 - <<'PY'
from pathlib import Path
p = Path('/tmp/ansible-netbird.html')
text = p.read_text(errors='ignore') if p.exists() else ''
for needle in ['netbirdio/ansible-netbird', 'GitHub', 'ansible-netbird']:
    print(needle, needle in text)
PY

Repository: netbirdio/docs

Length of output: 278


🏁 Script executed:

git ls-files 'src/pages/**' | rg '(^|/)api(/|$)|(^|/)introduction\.(md|mdx)$|(^|/)index\.(md|mdx)$'
rg -n '"/api/introduction"|/api/introduction|api/introduction' src/pages src/components src/lib

Repository: netbirdio/docs

Length of output: 2436


🏁 Script executed:

git ls-files | rg '^(src/pages|pages|public|vercel|next)\b|redirect|rewrite|route'
rg -n 'api/introduction|/api/introduction|public-api|redirect|rewrite|trailingSlash|basePath' src public . --glob '!node_modules' --glob '!dist' --glob '!build'

Repository: netbirdio/docs

Length of output: 50371


🏁 Script executed:

cat -n next.config.mjs | sed -n '1,220p'

Repository: netbirdio/docs

Length of output: 9690


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

# Check for any source files that would map to /api/introduction
files = [p.as_posix() for p in Path('.').resolve().glob('src/pages/**/*') if p.is_file()]
matches = [f for f in files if re.search(r'(^|/)api(/|$)|(^|/)introduction\.(md|mdx)$', f)]
print("matches:", "\n".join(matches) if matches else "<none>")

# Check next.config.mjs for /api redirects/rewrites
text = Path('next.config.mjs').read_text()
for needle in ['/api/introduction', '/api/:path*', 'destination: \'/api/:path*\'', 'source: \'/api']:
    print(needle, needle in text)
PY

Repository: netbirdio/docs

Length of output: 349


Fix the stale /api/introduction link
/api/introduction doesn’t resolve in this docs site; point it at the existing public API page (for example /manage/public-api) or add a redirect.

🧰 Tools
🪛 LanguageTool

[style] ~134-~134: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...RL without a trailing /api segment. - Confirm the PAT has not expired or been revoked...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~150-~150: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...0 seconds by default. On a slow link or a very large tenant, raise it with the timeout par...

(EN_WEAK_ADJECTIVE)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/selfhosted/iac/ansible.mdx` around lines 1 - 160, The docs page
currently links the NetBird REST API to a stale internal path, so update the
reference in the description/introduction content to the existing public API
docs target instead. Fix the markdown link that points to the API introduction
from the `description` export or nearby prose in `ansible.mdx`, and use the
public API page path already used elsewhere in the site (or add a redirect if
that’s the chosen docs pattern).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant