# NetBox — IPAM + DCIM, candidate "source of truth for network facts". # THIS IS AN EVALUATION DEPLOYMENT (see CONTEXT.md §1: adoption is not decided). # Nothing else in the repo reads from NetBox yet; it is safe to delete outright. # # Chart: netbox/netbox 8.3.38 (app v4.6.5) # helm repo add netbox https://netbox-community.github.io/netbox-chart/ # # Shape mirrors the other k3s services here: external shared Postgres, private # exposure over the Tailscale ingress, authentication delegated to Authelia OIDC. # Pin the app image; the chart default matches but drifts on every chart bump. image: tag: v4.6.5 # --- Local break-glass superuser ------------------------------------------- # Deliberately kept, exactly like Grafana's local admin: NetBox's SSO group-> # superuser mapping does not exist (that feature is LDAP-only, see README), so # the first SSO login lands as an ordinary user and needs promoting BY someone. # Password is generated by the chart and preserved in the netbox-superuser Secret. superuser: name: admin email: admin@ad.ddupan.top # NetBox refuses writes via any hostname not listed here. The pod IP is appended # automatically (allowedHostsIncludesPodIP) for the k8s probes. allowedHosts: - netbox.ad.ddupan.top - netbox.netbox.svc.cluster.local - localhost # Envoy terminates TLS and forwards plain HTTP, so Django sees an http:// request # carrying an https:// Origin header. Without this, every POST (including the login # form) fails CSRF verification — it presents as "login is broken", not "config is # missing". Same requirement behind any TLS-terminating proxy. csrf: trustedOrigins: - https://netbox.ad.ddupan.top # It is an infrastructure source of truth on a private tailnet, but "on the # tailnet" is not authentication — require a login for read access too. # # This logs a benign FutureWarning at startup ("LOGIN_REQUIRED is deprecated ... # can be removed from your configuration file"): NetBox v5.0 drops the setting and # makes login mandatory, i.e. true is the direction of travel. The warning is # unavoidable via this chart — the ConfigMap always emits the key, and NetBox warns # on false as well ("unauthenticated access will no longer be supported"). loginRequired: true # Everything else in this homelab is UTC (the node is Etc/UTC); stay consistent # so timestamps line up with VictoriaLogs. timeZone: UTC # Version check phones home to api.github.com. The WAN flaps (CONTEXT.md §6) and # a blocking outbound call on page render is exactly what we don't want. releaseCheck: url: "" # --- Authentication: Authelia forward-auth (trusted headers) -------------- # Authelia authenticates + enforces 2FA at the GATEWAY (securitypolicy.yaml); by the # time a request arrives here it is already authenticated, and Envoy has attached # Remote-* headers describing who the user is. # # WHY NOT OIDC (which this used to be): NetBox has NO SSO group -> role mapping. # REMOTE_AUTH_SUPERUSER_GROUPS and AUTH_LDAP_USER_FLAGS_BY_GROUP are LDAP-only; the # social-auth pipeline only runs user_default_groups_handler, so an OIDC user landed # as an ordinary member of one static group and had to be promoted BY HAND, and AD # group changes never propagated. With header auth, REMOTE_AUTH_GROUP_SYNC_ENABLED # re-evaluates group membership on EVERY request — the same declarative AD-group # pattern already used by Grafana and Proxmox. # # ⚠⚠ THIS SETTING IS ONLY SAFE BEHIND THE GATEWAY. RemoteUserBackend trusts the # header unconditionally — NetBox has no trusted-proxy allowlist. Two things keep # that honest, and BOTH must stay true: # 1. Envoy sets these headers from Authelia's response, and Envoy Gateway's # headersToBackend OVERRIDES any client-supplied value ("coexisting headers # will be overridden"), so a spoofed Remote-User cannot survive the hop. # 2. networkpolicy.yaml restricts pod ingress to the gateway namespace, so nothing # in-cluster can bypass Envoy and talk to :8080 directly. # Removing either one turns `Remote-User: admin` into an instant superuser. remoteAuth: enabled: true backends: - netbox.authentication.RemoteUserBackend # Authelia's header names. NetBox's defaults assume HTTP_REMOTE_USER_GROUP, but # Authelia emits Remote-Groups -> HTTP_REMOTE_GROUPS, and joins values with a # COMMA where NetBox defaults to "|". Both must be overridden or group sync # silently yields one group literally named "a,b,c". header: HTTP_REMOTE_USER groupHeader: HTTP_REMOTE_GROUPS groupSeparator: "," # Authelia sends Remote-Email and Remote-Name (a single display name); it has no # split given/family name, so the first/last-name headers are left unmapped. userEmail: HTTP_REMOTE_EMAIL autoCreateUser: true autoCreateGroups: true # mirror AD groups into NetBox groups as they appear groupSyncEnabled: true # re-evaluate membership on every request # AD group -> NetBox role. Managed in ../../infrastructure/samba-ad (samba_ad_groups). superuserGroups: - netbox-admins staffGroups: - netbox-admins # is_staff => access to the Django admin site # The OIDC client config that used to live here (SOCIAL_AUTH_OIDC_*) is gone; the # netbox-secrets Secret now only carries the database password. extraConfig: [] # --- Postgres: the shared CNPG cluster ------------------------------------ # Dedicated role + database, no shared superuser — same as Authelia and Gitea. postgresql: enabled: false externalDatabase: host: shared-postgresql-rw.shared-db.svc.cluster.local port: 5432 database: netbox username: netbox existingSecretName: netbox-secrets existingSecretKey: postgresql-password # --- Valkey (Redis) for the RQ task queue + caching ----------------------- # Bundled subchart rather than a shared instance: nothing else in the cluster # runs Redis, and NetBox wants two logical databases of its own. # # ⚠ Bitnami's public catalog no longer serves versioned tags (only `latest`, # versioned images moved to the `bitnamilegacy` repo), which is why the chart # ships `tag: latest`. `latest` is not reproducible, so pin the DIGEST instead — # resolved 2026-07-25 for bitnami/valkey:latest. valkey: image: digest: sha256:5d43ca8bb57aa263ef78d1684dbf1e4b4f63844727eafdd5ea2b0e102a25b141 # Single node: the chart default is primary+replica, which buys nothing here # (one k8s node, no HA anywhere in this cluster — CONTEXT.md §6). architecture: standalone primary: persistence: enabled: true storageClass: localpv-zfs-ceph size: 1Gi resourcesPreset: micro # --- Storage -------------------------------------------------------------- # Media = uploaded images/attachments only; the real data is in Postgres. # # ⚠ NOT localpv-zfs-ceph, unlike every other PVC here. The chart mounts this ONE # RWO claim into BOTH Deployments (web and worker — the worker writes files the web # serves), and the OpenEBS ZFS class provisions a **zvol**: a block device with xfs # on it, which cannot be mounted twice. Whichever pod won the race mounted it and # the other stuck in Init forever with # verifyMount: device already mounted at [...] # from the CSI node plugin. local-path is hostPath-based, so kubelet can bind-mount # the same directory into both pods. (localpv-zfs-ceph stays correct for the valkey # PVC below — single StatefulSet pod, no sharing.) persistence: enabled: true storageClass: local-path size: 2Gi # --- LAN exposure via the Contour gateway --------------------------------- # https://netbox.ad.ddupan.top — reachable from any LAN host, no Tailscale client # needed, and it does not traverse the WAN. Deliberately NOT on the cloudflared # tunnel: a full inventory of the network is not something to publish. # # Gateway API, not Ingress. The shared gateway lives in ../../platform/envoy-gateway and already # terminates TLS with the *.ad.ddupan.top wildcard from ../../platform/cert-manager — so this # service needs no cert of its own, just this route plus a DNS A record on the DC # (../../infrastructure/samba-ad). Authentication is enforced at the gateway by the SecurityPolicy in # securitypolicy.yaml, before a request ever reaches this pod. ingress: enabled: false httpRoute: enabled: true parentRefs: - name: eg namespace: envoy-gateway-system sectionName: https # the :443 listener; :80 is for ACME/redirects only hostnames: - netbox.ad.ddupan.top # --- Resource budget ------------------------------------------------------ # The laptop is the single k3s node and already runs everything (CONTEXT.md §6); # at survey time it had ~4 GiB RAM headroom. Keep this deployment modest. resourcesPreset: medium # 500m/1Gi requests, 750m/1.5Gi limits worker: resourcesPreset: small # 500m/512Mi requests — one worker, no bulk jobs yet # Nightly changelog/job pruning. Retentions are the chart defaults (90 days). housekeeping: enabled: true schedule: "17 4 * * *"