I wanted a simple source of truth for my home network: physical hosts, virtual machines, VLANs, prefixes, IP addresses, interfaces and switch connections. I was already keeping some of this information in NocoDB, but once the relationships between devices, interfaces and IPs become important, NetBox is a much better fit.

The official installation documentation targets Linux, but NetBox itself is a Python/Django application backed by PostgreSQL and Redis. With a few FreeBSD-specific adjustments it runs cleanly inside a jail.

Architecture

FreeBSD 15.1-RELEASE host
└── netbox VNET jail
    ├── nginx          :80
    │    └── Gunicorn  127.0.0.1:8001
    │         └── NetBox
    ├── PostgreSQL 17
    ├── Redis
    └── NetBox RQ worker

Everything runs natively in one jail using normal FreeBSD services.

Why a jail?

  • Native FreeBSD deployment without Docker
  • No Linux VM just for NetBox
  • PostgreSQL, Redis and nginx come from normal FreeBSD packages
  • ZFS snapshots make rollback easy
  • VNET gives the jail its own LAN address
  • The application can be managed with standard service commands

Creating the jail

Create a VNET jail on the FreeBSD host. Adjust the address and interface for your network:

bastille create -V netbox 15.1-RELEASE 192.168.2.55/24 vtnet0

Before starting PostgreSQL, give the jail private System V IPC namespaces:

bastille config netbox set sysvmsg new
bastille config netbox set sysvsem new
bastille config netbox set sysvshm new
bastille restart netbox

Without these settings PostgreSQL initdb fails with:

FATAL: could not create shared memory segment: Function not implemented
DETAIL: Failed system call was shmget(...)

Enter the jail:

bastille console netbox

Installing NetBox

The bootstrap script installs the full stack:

fetch https://codeberg.org/dkade/BSD/raw/branch/main/FreeBSD/netbox/install_netbox_freebsd.sh
chmod +x install_netbox_freebsd.sh

NETBOX_HOST=netbox.home.lan \
NETBOX_IP=192.168.2.55 \
./install_netbox_freebsd.sh

The current script is pinned to NetBox 4.6.1 and installs:

  • Python 3.12
  • PostgreSQL 17
  • Redis
  • Gunicorn
  • nginx

NetBox itself lives under:

/usr/local/netbox/
├── src/
├── venv/
└── gunicorn.py

The installer creates native FreeBSD services for:

netbox
netbox_rq

and enables the standard services:

postgresql
redis
nginx

FreeBSD service handling

This was the main FreeBSD-specific part.

A first implementation used:

daemon
└── su
    └── gunicorn

It started successfully, but stop and restart could hang because the FreeBSD service framework was tracking a supervisor process instead of the actual Gunicorn master.

The working version lets Gunicorn daemonize itself and maintain:

/var/run/netbox/netbox.pid

The rc.d script then signals the real Gunicorn master directly.

The RQ worker uses the same principle and stores its real PID at:

/var/run/netbox/netbox-rq.pid

This gives normal FreeBSD behaviour:

service netbox start
service netbox stop
service netbox restart
service netbox status

service netbox_rq start
service netbox_rq restart
service netbox_rq status

nginx

nginx listens on port 80 and proxies to Gunicorn on localhost:

client
  |
  v
nginx :80
  |
  v
Gunicorn 127.0.0.1:8001
  |
  v
NetBox

The generated configuration lives at:

/usr/local/etc/nginx/conf.d/netbox.conf

Creating the administrator

After installation:

cd /usr/local/netbox/src/netbox

su -m netbox -c \
  '/usr/local/netbox/venv/bin/python manage.py createsuperuser'

Then open the hostname or jail IP in a browser.

ALLOWED_HOSTS and CSRF

Django validates both request hosts and POST origins.

The installer adds the configured hostname and optional jail IP to:

ALLOWED_HOSTS = [
    'netbox.home.lan',
    '192.168.2.55',
    'localhost',
    '127.0.0.1',
]

CSRF_TRUSTED_ORIGINS = [
    'http://netbox.home.lan',
    'http://192.168.2.55',
]

If the origin is missing, the UI can load normally but login fails with:

Forbidden (403)
CSRF verification failed. Request aborted.

If HTTPS is added later, add the matching https:// origin as well.

Checking the stack

service netbox status
service netbox_rq status
service postgresql status
service redis status
service nginx status

Gunicorn should only listen on localhost:

sockstat -4 -l | grep 8001

nginx should listen on port 80:

sockstat -4 -l | grep ':80'

Upgrading

Before upgrading I would take a ZFS snapshot and dump PostgreSQL:

pg_dump -U netbox -h 127.0.0.1 netbox > /root/netbox.sql

Then stop the application:

service netbox_rq stop
service netbox stop

For now the installer intentionally stays pinned to a known working NetBox release rather than automatically following the latest tag. A separate upgrade script can later handle release download, Python dependency updates, migrations, static file collection and service restart.

Gotchas

  • System V IPC: PostgreSQL needs sysvmsg=new, sysvsem=new and sysvshm=new in the Bastille jail.
  • Service user shell: /bin/sh avoids the su and user-environment problems we hit with /usr/sbin/nologin.
  • Gunicorn 26 runtime path: set XDG_RUNTIME_DIR=/var/run/netbox so its control socket is created in a directory owned by the NetBox user.
  • Avoid daemon -> su -> gunicorn: startup works, but signal propagation makes stop/restart unreliable.
  • Track the real RQ worker PID: the same supervisor problem applies to the background worker.
  • CSRF matters: any hostname or IP used in the browser must be represented in the Django host/origin configuration.
  • Static docs directory: release archives may omit project-static/docs; creating it avoids Django’s static-files warning.
  • FreeBSD is not NetBox’s officially documented installation target: future upgrades may need extra ports/packages when Python dependencies change.

Summary

NetBox runs well natively inside a Bastille VNET jail once the service supervision and PostgreSQL IPC details are handled.

The final stack is simple:

FreeBSD jail
├── nginx
├── NetBox / Gunicorn
├── NetBox RQ
├── PostgreSQL
└── Redis

No Docker, no Linux VM, and the whole application behaves like a normal FreeBSD service.


Disclaimer: I use AI as a productivity tool. For a senior engineer, AI is incredibly powerful as one can focus on the solution design and conceptualization and leave the boring part that is implementation to the AI.