A step-by-step guide to build the NetBox Docker image, push it to a container registry, and deploy it on OpenShift.
Route (TLS/edge)
│
▼
NetBox Service (port 8080)
│
├── NetBox App (Granian, 4 workers)
├── NetBox Worker (RQ sidecar)
│
├── PostgreSQL 18 (persistent)
├── Redis Sessions (Valkey 9, persistent)
└── Redis Cache (Valkey 9, persistent)
- Podman installed (
podman build/podman push) - Container registry account with a repository
- OpenShift cluster access (
ocCLI configured) - Podman login to registry:
podman login ${REGISTRY}
git clone https://github.com/netbox-community/netbox-docker.git
cd netbox-docker# Container registry (default: quay.io)
export REGISTRY=quay.io
# Choose the NetBox version tag (no "v" prefix — e.g., 4.3.0, 4.2.0, 4.1.0)
export NETBOX_VERSION=4.3.0
# Your registry organization
export REGISTRY_ORG=your-registry-org
⚠️ NetBox 3.4.x Compatibility Note: NetBox 3.4.x pins Django 3.2.x which is incompatible with Python 3.12 (Ubuntu 24.04). Additionally,django-filterin NetBox 3.4.x requiresdjango.utils.itercompatwhich was removed in Django 4.0 — so even upgrading Django won't fix it. If deploying NetBox 3.4.x, use Ubuntu 22.04 as the base image:export BASE_IMAGE="docker.io/ubuntu:22.04"The
build-and-push.shscript defaults to Ubuntu 22.04 and preserves the Django pin. Also remove theunitpackage references from the Dockerfile (replace withnginx) sinceunitpackages are Ubuntu 24.04-only.For NetBox 4.x+, Ubuntu 24.04 works without issues.
| Base Image | Python | Django Required | NetBox Versions | Patches Needed |
|---|---|---|---|---|
| Ubuntu 24.04 | 3.12 | ≥ 4.2 | 4.3+ (no patches) | None for 4.3+ |
| Ubuntu 24.04 | 3.12 | ≥ 4.2 | 3.4.x — not compatible | Django 3.2.x does not support Python 3.12 + django-filter breaks with Django 4.0+ |
| Ubuntu 22.04 | 3.10 | 3.2 — 4.2 | 3.4.x, 4.0 — 4.3 | Minimal patches (see below) |
Why this matters: Each Ubuntu release ships a different Python version, and Django dropped support for older Python versions in each major release. NetBox pins Django versions, so the chain is: Ubuntu → Python → Django → NetBox.
Key dependency conflicts by Python version:
| Package | Python 3.10 (Ubuntu 22.04) | Python 3.12 (Ubuntu 24.04) |
|---|---|---|
| Django | 3.2 — 4.2 ✓ | Requires ≥ 4.2 (3.2/4.0/4.1 do NOT support 3.12) |
| sentry-sdk | Pin ==1.11.1 works |
Pin conflicts with sentry-sdk[django]>=2.x — remove pin |
| PyYAML | Pin ==6.0 builds OK |
cython_sources attr error — remove pin, use ≥ 6.0.2 |
| Pillow | Pin works | Source build needs libjpeg-dev — remove pin + add header |
| jsonschema | Pin ==3.2.0 works |
3.2.0 incompatible with 3.12 — remove pin, use ≥ 4.0 |
| django-auth-ldap | ==5.2.0 requires Django ≥ 4.2 |
Same — use ==4.8.0 when Django < 4.2 |
| lxml | --no-binary builds OK |
Cython API changed — remove --no-binary, pin >=5.0.0 |
| social-auth-core | sed bracket handling OK | Same issue — needs pipe-delimiter fix in Dockerfile sed |
Rule of thumb: If NETBOX_VERSION < 4.3, always use Ubuntu 22.04 as the base image. The build-and-push.sh script handles this automatically.
git clone https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
# 1. Checkout the netbox-docker version
git fetch --tags
git tag -l '5.*' | sort -V | tail -20 # List recent tags
git checkout ${NETBOX_VERSION}
# 2. Clone the NetBox source code (required — Dockerfile needs it)
# The netbox source repo uses v-prefixed tags (v4.0.2, not 4.0.2)
git clone --depth 1 --branch "v${NETBOX_VERSION}" \
https://github.com/netbox-community/netbox.git .netbox
# 3. Patch the Dockerfile (use Python heredoc — sed can't match nested quotes)
# Add libjpeg-dev for Pillow build
sed -i '/libxslt-dev/i\ libjpeg-dev \\' Dockerfile
# Fix build-time sed delimiter and skip mkdocs build (use Python heredoc — sed can't match nested quotes)
python3 << 'PYEOF'
with open('Dockerfile') as f:
c = f.read()
# Fix social-auth-core sed: use | delimiter to avoid / conflict with ] in replacement
c = c.replace(
"sed -i -e 's/social-auth-core/social-auth-core\\[all\\]/g'",
"sed -i -e 's|social-auth-core|social-auth-core\\[[^]]*\\]/social-auth-core[all]|g'"
)
# Skip mkdocs build — mkdocs-autorefs is incompatible with Python 3.12
c = c.replace(
'SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python -m mkdocs build',
"echo 'Skipping mkdocs build (incompatible with Python 3.12)' #"
)
with open('Dockerfile', 'w') as f:
f.write(c)
PYEOF
# 4. Remove hard pins from NetBox source requirements (Python 3.12 compatibility)
sed -i '/^sentry-sdk==/d' .netbox/requirements.txt
sed -i '/^PyYAML==/d' .netbox/requirements.txt
sed -i '/^Pillow==/d' .netbox/requirements.txt
# ⚠️ Django: DO NOT remove this pin for NetBox 3.x — django-filter requires
# django.utils.itercompat (removed in Django 4.0). Only remove for NetBox 4.3+
sed -i '/^Django==/d' .netbox/requirements.txt
sed -i '/^jsonschema==/d' .netbox/requirements.txt
sed -i '/^social-auth-core\[.*\]==/d' .netbox/requirements.txt
# 5. Fix django-auth-ldap version conflict
sed -i 's/^django-auth-ldap==5.2.0$/django-auth-ldap==4.8.0/' requirements-container.txt
# 6. Remove --no-binary flags + pin lxml for Python 3.12 compatibility
sed -i '/^--no-binary lxml/d' requirements-container.txt
sed -i '/^--no-binary xmlsec/d' requirements-container.txt
echo "lxml>=5.0.0" >> requirements-container.txt
# 7. Verify the patches took effect
grep "libjpeg-dev" Dockerfile
grep "social-auth-core\|[^]]*\|/social-auth-core\[all\]" Dockerfile # should show pipe delimiters
grep "Skipping mkdocs" Dockerfile # should exist
grep -E "^(sentry-sdk|PyYAML|Pillow|jsonschema)==[^ ]" .netbox/requirements.txt || echo "Pins removed ✓"
# For NetBox 4.x+: Django pin should be removed
# For NetBox 3.x: Django pin should be kept (do not run the sed command above)
grep "Django==" .netbox/requirements.txt && echo " ✅ Django pin kept (NetBox 3.x)" || echo " ✅ Django pin removed (NetBox 4.x+)"
grep "lxml>=5.0.0" requirements-container.txt # should exist
grep "^--no-binary" requirements-container.txt || echo "--no-binary removed ✓"
# 8. Build (--no-cache prevents podman from using stale cached layers)
podman build \
--no-cache \
--pull \
--target main \
-f Dockerfile \
-t "${REGISTRY}/${REGISTRY_ORG}/netbox:${NETBOX_VERSION}" \
--build-arg "FROM=docker.io/ubuntu:24.04" \
--build-arg "NETBOX_PATH=.netbox" \
.This clones both repos, patches the Dockerfile for package name compatibility, builds the image, and tags it. Verify it works:
podman run --rm ${REGISTRY}/${REGISTRY_ORG}/netbox:${NETBOX_VERSION} --helppodman push ${REGISTRY}/${REGISTRY_ORG}/netbox:${NETBOX_VERSION}Or use the convenience scripts which handle everything automatically:
# Ubuntu 24.04 base (recommended)
./build/build-and-push-24.04.sh ${NETBOX_VERSION} ${REGISTRY_ORG} ${REGISTRY}
# Ubuntu 22.04 base
./build/build-and-push.sh ${NETBOX_VERSION} ${REGISTRY_ORG} ${REGISTRY}| Variable | Default | Description |
|---|---|---|
REGISTRY |
quay.io |
Container registry host |
REGISTRY_ORG |
(required) | Your registry organization |
FROM |
docker.io/ubuntu:24.04 |
Base image |
oc new-project netboxOption A: Use the template manifest (recommended)
Edit manifests/netbox-image-pull-secret.yaml and replace the placeholder .dockerconfigjson value with your actual registry credentials, then apply:
oc apply -f manifests/netbox-image-pull-secret.yaml
⚠️ The manifest contains a placeholder value — you must replace it before deploying.
Option B: Create from scratch
oc create secret docker-registry netbox-image-pull-secret \
--docker-server=${REGISTRY} \
--docker-username=YOUR_QUAY_USERNAME \
--docker-password=YOUR_QUAY_TOKEN \
--docker-email=you@example.comOpenShift 4.x enforces [Security Context Constraints](https://docs.openshift.com/latest/security/securing Applications_and_Projects/configuring-scc.html). All manifests in this repo are configured to work with the restricted-v2 SCC — each container specifies securityContext with runAsNonRoot, allowPrivilegeEscalation: false, and capabilities.drop: ["ALL"].
If you get unable to validate against any security context constraint, check:
- Your ServiceAccount has the correct SCC bound:
oc describe scc restricted-v2 - No pod-level
fsGroupis set (use container-level security instead) - All containers have
securityContextdefined
All manifests live in the manifests/ directory. Apply them in order.
oc apply -f manifests/netbox-config.yaml
oc apply -f manifests/netbox-env.yaml
oc apply -f manifests/netbox-image-pull-secret.yaml # for private registriesImportant: Edit manifests/netbox-env.yaml and set real passwords and a strong SECRET_KEY (minimum 50 characters). The defaults are placeholders.
# PostgreSQL (with PVC)
oc apply -f manifests/postgres.yaml
# Redis session store (with PVC)
oc apply -f manifests/redis.yaml
# Redis cache (with PVC)
oc apply -f manifests/redis-cache.yamloc wait --for=condition=ready pod -l app=netbox-postgres -n netbox --timeout=120s
oc wait --for=condition=ready pod -l app=netbox-redis -n netbox --timeout=60s
oc wait --for=condition=ready pod -l app=netbox-redis-cache -n netbox --timeout=60sEdit manifests/netbox.yaml and replace ${REGISTRY}/${REGISTRY_ORG}/netbox:${NETBOX_VERSION} with your actual image path.
oc apply -f manifests/netbox.yamloc apply -f manifests/route.yamloc get pods -n netbox
oc get route netbox -n netbox -o jsonpath='{.spec.host}'Visit the URL in your browser. The first startup takes ~90 seconds for migrations and superuser creation.
# Get the route URL
oc get route netbox -n netbox -o jsonpath='{.spec.host}'
# e.g., https://netbox-netbox.apps.cluster.example.comDefault admin credentials (from netbox-env.yaml):
- Username:
admin - Password: whatever you set for
SUPERUSER_PASSWORD
If you've lost the admin password or want to change it after deployment:
Option A: Interactive (prompts for new password)
POD=$(oc get pods -n netbox -l app=netbox -o jsonpath='{.items[0].metadata.name}')
oc exec -it $POD -n netbox -c netbox -- python3 /opt/netbox/netbox/manage.py changepassword adminOption B: One-liner (set password without prompt)
POD=$(oc get pods -n netbox -l app=netbox -o jsonpath='{.items[0].metadata.name}')
oc exec $POD -n netbox -c netbox -- python3 -c "
from django.contrib.auth.models import User
u = User.objects.get(username='admin')
u.set_password('YOUR_NEW_PASSWORD')
u.save()
print('Password changed successfully')
"Note: The NetBox Docker image has
manage.pyat/opt/netbox/netbox/manage.py— not at the root. Runningpython3 manage.pywithout the full path will fail withNo such file or directory.
To add more replicas of the app (worker stays at 1):
oc scale deployment netbox -n netbox --replicas=2- Rebuild and push a new image tag (Step 1)
- Update the image in
manifests/netbox.yaml - Apply:
oc apply -f manifests/netbox.yaml
openshift_netbox/
├── .ggignore # GitGuardian ignore (placeholder secrets)
├── README.md # This guide
├── build/
│ ├── build-and-push.sh # Build with Ubuntu 22.04 base
│ └── build-and-push-24.04.sh # Build with Ubuntu 24.04 base (patches Dockerfile)
└── manifests/
├── netbox-config.yaml # Configuration ConfigMap
├── netbox-env.yaml # Environment variables Secret (placeholders)
├── netbox-image-pull-secret.yaml # Image pull secret template (placeholder)
├── postgres.yaml # PostgreSQL deployment + PVC
├── redis.yaml # Redis session store + PVC
├── redis-cache.yaml # Redis cache + PVC
├── netbox.yaml # NetBox app + worker deployment
└── route.yaml # OpenShift Route (TLS ingress)
Check the ImagePullSecret:
oc describe pod <netbox-pod> -n netbox | grep -A5 "Failed to pull"
oc get sa default -n netbox -o yaml | grep imagePullSecretsCheck logs:
oc logs <netbox-pod> -n netbox -c netbox
oc logs <netbox-pod> -n netbox -c netbox-workerCommon causes: wrong DB password, invalid SECRET_KEY format, or DB not ready.
ModuleNotFoundError: No module named 'django.utils.itercompat'
Cause: The image was built with Django 4.x but NetBox 3.4.x's django-filter
requires django.utils.itercompat which was removed in Django 4.0. This happens
when the build script removes the Django pin, letting uv resolve to Django 4.x.
Fix: Rebuild the image using build-and-push.sh (Ubuntu 22.04) which preserves
the Django pin. For Ubuntu 24.04, NetBox 3.x is not compatible — see the
compatibility note.
The probe checks /login/ on port 8080 with a 90-second initial delay. If the first startup takes longer (large migrations), increase initialDelaySeconds in manifests/netbox.yaml.
Pillow needs libjpeg-dev headers to build from source. The Dockerfile doesn't install it.
Fix: Add libjpeg-dev to the apt-get install list and remove the Pillow pin:
cd netbox-docker
sed -i '/libxslt-dev/i\ libjpeg-dev \\' Dockerfile
sed -i '/^Pillow==/d' .netbox/requirements.txtThe upstream Dockerfile sed creates social-auth-core[[all]] instead of social-auth-core[all].
Fix: Patch the Dockerfile sed command using a Python heredoc:
cd netbox-docker
python3 << 'PYEOF'
with open('Dockerfile') as f:
c = f.read()
c = c.replace(
"sed -i -e 's/social-auth-core/social-auth-core\\[all\\]/g'",
"sed -i -e 's|social-auth-core|social-auth-core\\[[^]]*\\]/social-auth-core[all]|g'"
)
with open('Dockerfile', 'w') as f:
f.write(c)
PYEOFsrc/lxml/etree.c:5604:45: error: 'PyThreadState' has no member named 'curexc_type'
NetBox 3.4.x requirements-container.txt has --no-binary lxml which forces
building lxml 4.6.5 from source. Its Cython code uses Python 3.8 APIs removed in
Python 3.12. Remove the --no-binary flags to let uv use pre-built wheels:
cd netbox-docker
sed -i '/^--no-binary lxml/d' requirements-container.txt
sed -i '/^--no-binary xmlsec/d' requirements-container.txtSee the Manual Build Steps section for all required patches.
Quick fix — use the convenience script which handles everything:
cd netbox-docker
git checkout v${NETBOX_VERSION}
git clone --depth 1 --branch "v${NETBOX_VERSION}" \
https://github.com/netbox-community/netbox.git .netbox
# Build (--no-cache is critical to avoid stale cached layers)
podman build \
--pull \
--no-cache \
--target main \
-f Dockerfile \
-t "${REGISTRY}/${REGISTRY_ORG}/netbox:${NETBOX_VERSION}" \
--build-arg "FROM=docker.io/ubuntu:24.04" \
--build-arg "NETBOX_PATH=.netbox" \
.Note: netbox-docker 3.4.1+ already uses correct
libxmlsec1andlibxmlsec1-opensslpackage names — no renaming patches needed for these versions.
Because you require sentry-sdk==1.11.1 and sentry-sdk[django]==2.39.0,
we can conclude that your requirements are unsatisfiable.
NetBox source .netbox/requirements.txt pins sentry-sdk==1.11.1 but netbox-docker's
requirements-container.txt requires sentry-sdk[django]>=2.x. Fix by removing
the hard pin from the NetBox source requirements:
sed -i '/^sentry-sdk==/d' .netbox/requirements.txtBecause django-auth-ldap==5.2.0 depends on django>=4.2 and you require django==4.1.4,
we can conclude that your requirements are unsatisfiable.
netbox-docker's requirements-container.txt pins django-auth-ldap==5.2.0 which
requires django>=4.2, but NetBox 3.4.x source doesn't pin django>=4.2 so uv
resolves django==4.1.4. Fix by downgrading django-auth-ldap:
sed -i 's/^django-auth-ldap==5.2.0$/django-auth-ldap==4.8.0/' requirements-container.txtAttributeError: 'build_ext' object has no attribute 'cython_sources'
PyYAML 6.0 requires Cython to build from source but doesn't declare it as a build dependency. Remove the hard pin so uv resolves to a newer version with pre-built wheels:
sed -i '/^PyYAML==/d' .netbox/requirements.txtThe upstream Dockerfile's sed command creates double brackets when requirements.txt already has social-auth-core[openidconnect] (NetBox 3.4.x and newer). The build scripts handle this automatically. For manual builds, use the Python heredoc patch shown in Step 3 or the Troubleshooting section above.