Skip to content

docs: fix default.json examples that stop the daemon from starting - #988

Merged
emrcbrn merged 1 commit into
netbirdio:mainfrom
emrcbrn:docs/fix-bootstrap-config-file
Sep 18, 2026
Merged

emrcbrn merged 1 commit into
netbirdio:mainfrom
emrcbrn:docs/fix-bootstrap-config-file

Conversation

@emrcbrn

@emrcbrn emrcbrn commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

What

Every default.json example on Bootstrap peers via config file contains at least one value the client cannot parse. A bad value there is fatal: the daemon exits rather than falling back to defaults, so a reader following this page gets a peer that never starts.

Found while investigating a community thread where a user bootstraps peers with Ansible and templates these files by hand.

The three defects

1. ManagementURL / AdminURL shown as strings. Config.ManagementURL is a *url.URL, which marshals as an object. There is no custom UnmarshalJSON in the package.

FATL failed to start daemon: failed to get config:
     json: cannot unmarshal string into Go struct field Config.ManagementURL of type url.URL

Reproduced on 0.69.0, 0.73.0, 0.78.2 and 0.79.0-rc.1, so this is not a regression; the example has never worked.

2. The Docker example bind-mounts the single file. The client rewrites default.json on first start (private key, SSH key) using temp-file-plus-rename, and a rename cannot replace a bind-mounted file:

FATL failed to start daemon: failed to get config:
     move /var/lib/netbird/.1478389823default.json to /var/lib/netbird/default.json:
     device or resource busy

This fails with and without :ro, so removing :ro is not the fix. Mounting the directory is.

3. The Kubernetes example mounts the ConfigMap at the file path via subPath — the same read-only single-file mount. Replaced with an init container that seeds a writable emptyDir.

Verification

Run against the real client in a systemd container, one scenario per case:

Mount shape Result
single file, :ro daemon dies (device or resource busy)
single file, writable daemon dies (same)
directory, :ro daemon dies (read-only file system, on active_profile.json)
directory, writable works — config loads, client reaches management login

The corrected default.json starts cleanly, keeps the templated values, and regenerates PrivateKey (44 chars), confirming the page's existing claim about leaving it empty.

Fields I checked and deliberately left alone, because they are correct: IFaceBlackList as an array, the empty PrivateKey, the platform path table (darwin and linux both resolve to /var/lib/netbird), and the status --check live|ready|startup values.

One limitation

The Kubernetes manifest is the only change not run end to end — no cluster was available in this environment. Both failure modes it avoids were reproduced directly with the equivalent Docker mounts, and the pattern it adopts is the one verified working, but the manifest itself deserves a look from someone with a cluster to hand.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Updated configuration examples to use {Scheme, Host} objects for management and administration URLs.
    • Documented validation errors for incorrect JSON types and clarified that the daemon exits rather than using default values.
    • Updated Docker guidance to mount a writable state directory.
    • Expanded Kubernetes deployment instructions with writable storage setup, fresh registration guidance, and persistent-volume alternatives.

@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

@emrcbrn is attempting to deploy a commit to the NetBird GmbH Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 9a1a388b-0e8f-4d3f-8b98-cb5aa4393f49

📥 Commits

Reviewing files that changed from the base of the PR and between 113d9d2 and 5224ddb.

📒 Files selected for processing (1)
  • src/pages/manage/peers/bootstrap-via-config-file.mdx

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: ed450664-94ee-465a-a137-37ab75d73163

📥 Commits

Reviewing files that changed from the base of the PR and between 94cc8ba and 113d9d2.

📒 Files selected for processing (1)
  • src/pages/manage/peers/bootstrap-via-config-file.mdx

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The documentation changes URL configuration fields to JSON objects and documents startup validation. Docker and Kubernetes instructions now provide writable state storage for daemon file updates and profile creation.

Changes

Configuration bootstrap

Layer / File(s) Summary
URL object format and startup validation
src/pages/manage/peers/bootstrap-via-config-file.mdx
ManagementURL and AdminURL now use Scheme and Host objects. Incorrect JSON types are documented as startup errors.
Docker writable state setup
src/pages/manage/peers/bootstrap-via-config-file.mdx
Docker copies default.json into a writable /var/lib/netbird directory.
Kubernetes state initialization
src/pages/manage/peers/bootstrap-via-config-file.mdx
Kubernetes copies ConfigMap content into writable state with an init container, then mounts that state into the NetBird container. The documentation covers fresh pod registration and persistent-volume alternatives.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Other

Suggested reviewers: sunsetdrifter

Merge Risk: ⚪ Minimal · up to 113d9

The updated examples use the client’s required URL format and provide writable daemon state, so the documented Docker and Kubernetes startup paths are ready to merge.

🚥 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 clearly states that the documentation fixes default.json examples that prevent the daemon from starting. It accurately summarizes the primary objective of the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR

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

A rabbit copies config with care
Writable state now waits there
URLs take object form
Init containers perform
Fresh pods hop into the air

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

Every `default.json` example on the bootstrap page had at least one value
the client cannot parse, and a bad value there is fatal: the daemon exits
rather than falling back to defaults.

- `ManagementURL` and `AdminURL` were shown as strings. `Config.ManagementURL`
  is a `*url.URL`, so the daemon dies with `cannot unmarshal string into Go
  struct field Config.ManagementURL of type url.URL`. Reproduced on 0.69.0,
  0.73.0, 0.78.2 and 0.79.0-rc.1.
- The Docker example bind-mounted the single file. The client rewrites
  `default.json` on first start via temp-file-plus-rename, and a rename cannot
  replace a bind-mounted file, so the daemon dies with `device or resource
  busy`. This happens with and without `:ro`. Mount the directory instead.
- The Kubernetes example mounted the ConfigMap at the file path with
  `subPath`, which is the same read-only single-file mount. Seed a writable
  `emptyDir` from the ConfigMap with an init container instead.

Verified in a systemd container against the real client: the corrected
`default.json` starts cleanly, keeps the templated values, and regenerates
`PrivateKey`. The fields left untouched (`IFaceBlackList` as an array, the
empty `PrivateKey`, the platform path table, `status --check` values) were
checked and are correct.

The Kubernetes manifest is the one change not run end to end: no cluster was
available. Both failure modes it avoids were reproduced directly with
equivalent mounts, and the pattern it uses is the verified-working one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@emrcbrn
emrcbrn force-pushed the docs/fix-bootstrap-config-file branch from 113d9d2 to 5224ddb Compare September 18, 2026 15:23
@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 18, 2026 4:08pm UTC

Request Review

@emrcbrn
emrcbrn merged commit 2c80536 into netbirdio:main Sep 18, 2026
3 of 4 checks passed
@emrcbrn
emrcbrn deleted the docs/fix-bootstrap-config-file branch September 18, 2026 16:25
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.

2 participants