NetBox¶
Purpose¶
NetBox provides DCIM (data centre infrastructure
management) and IPAM (IP address management): the source of truth for devices, racks, prefixes, and
IP allocations. It runs internal-only in the default namespace: a web frontend, an RQ background
worker (webhooks, scripts, reports), and a daily housekeeping CronJob.
Design decisions¶
NetBox is one of the few apps here that does not use the bjw-s app-template chart. It uses the
official netbox-chart (oci://ghcr.io/netbox-community/netbox-chart/netbox) via an
OCIRepository + chartRef, because NetBox's multi-workload topology (web + worker + housekeeping)
and its existingSecret contract are already modelled by the upstream chart.
- Namespace
default, modelled on the in-repopaperlessapp (its closest twin). - PostgreSQL: shared CNPG cluster (
postgres18-rw.database.svc.cluster.local,sslmode=require); bundled Postgres disabled. Aninit-dbinitContainer (using thepostgres-initimage) creates thenetboxrole + database using the CNPG superuser before first boot. - Redis: external Dragonfly (
dragonfly.database.svc.cluster.local:6379); the chart's bundled cache (valkey.enabled) is disabled. Separate logical DB indices for the tasks queue (0) vs the cache (1). - Media: a local
ceph-blockPVC consumed viapersistence.existingClaimand backed up by the VolSync component (VOLSYNC_CAPACITY). NetBox uses no object storage; the cluster's S3 tier is Garage (storagenamespace), not Ceph RGW (which was removed). - Auth / exposure: superuser-only first pass (pocket-id OIDC is a fast-follow); internal-only
HTTPRoute on
envoy-internal, never publicly exposed. - Health monitored automatically by the gatus-sidecar (it auto-discovers the HTTPRoute);
reloader.stakater.com/autorolls pods on secret/config change; a ServiceMonitor exposes metrics to kube-prometheus-stack.
Deploy gotchas¶
- One shared
netbox-secretmust carry the chart's EXACT key names. All of the chart's secret references (globalexistingSecret,superuser.existingSecret,externalDatabase/tasksDatabase/cachingDatabaseexistingSecretName) point at the singlenetbox-secret, so that Secret must contain every key the chart projects, spelled exactly as the chart expects: secret_key,email_password(the chart's projectedsecretsvolume requires the key to exist even when email is unused, so set it to an empty string).passwordandapi_token(superuser).db_password(the value ofexternalDatabase.existingSecretKey).tasks_passwordandcache_password(the Dragonfly tasks/caching DB keys).-
These mismatches do not show up in a
flate(local) or Konflate (in-cluster PR render): the manifest is valid. They only fail at runtime as podFailedMount(references non-existent secret key) orCreateContainerConfigError(couldn't find key …). Before wiring the ExternalSecret, render the chart and grep the output for everysecretKeyRefkey:and projected-volumeitems[].key: -
Web, worker, and housekeeping share one RWO
mediaPVC, so they must co-locate. With a ReadWriteOnceceph-blockclaim, scheduling the pods onto different nodes deadlocks onMulti-Attachduring a rollout. Pin them together with podAffinity (anchor on the worker) or hit a stuck rollout. - Granian web OOMs at the chart's default 4 workers (peaks ~956Mi against a 1Gi limit). Set
GRANIAN_WORKERS=2and give the web pod request 512Mi / limit 1.5Gi. - Django host guarding rejects unlisted Host headers, including kubelet probes.
ALLOWED_HOSTSmust include both the route host(s) and the in-cluster service name (the chart'sallowedHostsIncludesPodIPcovers the pod IP), and CSRF trusted origins must listhttps://netbox.${SECRET_DOMAIN}. Miss this and probes (and the UI) get a 400. SECRET_KEYmust be 50+ characters. Generate it long enough (e.g.openssl rand -base64 60) or NetBox refuses to start.- The 1Password source item lives in the
Talosvault and is created via theopCLI (never the UI), with values generated locally.
Operational notes¶
- Reconcile lives in the
defaultnamespace; itdependsOncloudnative-pg-clusteranddragonfly-clusterin thedatabasenamespace, so reconcile those first if NetBox shows "dependency not ready".
- Hostname is
netbox.${SECRET_DOMAIN}(internal DNS via external-dns). The HTTPRoute's only hostname sits on theenvoy-internallistener, which is what keeps it internal-only. It does not expose NetBox publicly. - First-boot check: confirm the
init-dbinitContainer reports thenetboxrole/database created (or already exists), then log in as the superuser (admin). - Backups: the
mediaPVC is snapshotted by VolSync. Thenetboxdatabase lives on the shared CNPG cluster and is backed up by CNPG's own path, not VolSync. - Teardown:
prune: trueremoves NetBox's Kubernetes resources on revert, but thenetboxdatabase on the shared CNPG cluster and the 1Password item persist and must be dropped manually for a clean teardown.