NetBox is the source of truth for network and infrastructure documentation. If you run a homelab or a small datacenter and want to know what IP belongs where, what VLANs exist, and which rack holds which server — NetBox is the tool for the job. This post walks through deploying it on a k3s cluster using the official Helm chart, FluxCD for GitOps, and HashiCorp Vault via ExternalSecrets for secret management.
Prerequisites
A working k3s cluster (this was tested on a Pi5/NUC mixed cluster)
FluxCD bootstrapped and managing your cluster
The external-secrets-operator installed and a ClusterSecretStore pointing at Vault
Vault with a KV v2 secrets engine enabled
Helm 3 (for inspection only — Flux handles the actual deployment)
The chart is referenced by version and values are injected via a ConfigMap that kustomize generates from values.yaml. The kustomizeconfig.yaml wires the generated ConfigMap name hash into the valuesFrom reference automatically.
Store the following paths in Vault before anything else. The structure matters — the chart’s configuration script reads each secret by key name.
# Django secret keyvault kv put netbox/secretkey \
secretKey="$(openssl rand -hex 50)"# Superuser accountvault kv put netbox/superuser \
username="admin"\
email="[email protected]"\
password="your-admin-password"\
api_token="$(openssl rand -hex 20)"# PostgreSQLvault kv put netbox/postgresql \
password="netbox-db-pass"\
postgresPassword="postgres-superuser-pass"# Redis (empty password is fine for internal-only)vault kv put netbox/redis \
password="redis-pass"# Email/SMTP (empty string if not using)vault kv put netbox/email \
password="email-pass"
Step 5: ExternalSecrets
This is where most people hit a wall. The chart projects multiple Kubernetes secrets into a single volume, and each secret must have exactly the right key names. The way to find them is:
From that output, the chart maps secrets like this:
Kubernetes Secret
Key
Mounted/Used as
netbox-secret
secret_key
Django SECRET_KEY
netbox-secret
email_password
SMTP password
netbox-superuser-secret
username
env SUPERUSER_NAME
netbox-superuser-secret
email
env SUPERUSER_EMAIL
netbox-superuser-secret
password
/run/secrets/superuser_password
netbox-superuser-secret
api_token
/run/secrets/superuser_api_token
netbox-postgresql-secret
password
DB user password
netbox-postgresql-secret
postgres-password
Bitnami PG superuser
Note that email_password lives in netbox-secret, not in the superuser secret. This is counterintuitive and not documented clearly — it caught me out multiple times.
persistence.enabled: false — local-path is ReadWriteOnce and doesn’t support media file persistence across replicas. Re-enable this if you add Longhorn or NFS.
allowedHosts: ["*"] — fine for a homelab; restrict to your actual hostname in production.
First boot takes 2–5 minutes. PostgreSQL initializes, then NetBox runs migrations, then the superuser is created. You’ll see the worker pod restart a few times while waiting for migrations — that’s normal.
Verifying the Deployment
kubectl get pods -n netbox
# All should be Running/Readykubectl get externalsecret -n netbox
# All should show READY=Truekubectl get helmrelease -n netbox
# Should show READY=True and the chart version
Accessing the UI (without Ingress)
Before you wire up your ingress or DNS, port-forward directly:
kubectl port-forward -n netbox svc/netbox 8000:80
Open http://localhost:8000 and log in with the credentials from netbox/superuser in Vault.
Note: If you get a Django Bad Request (400), add "localhost" or "*" to allowedHosts in values.yaml. Django rejects requests from hosts not explicitly in the allowlist.