modified: README.md modified: compose.yml modified: configuration.py new file: haproxy.cfg new file: init-patroni-users.sql renamed: entrypoint.sh -> netbox-entrypoint.sh renamed: Containerfile -> netbox.Containerfile modified: nginx.conf new file: patroni-entrypoint.sh new file: patroni.Containerfile new file: patroni.yml
Netbox Stack
This project provides a fully working Netbox Installation connected to a BIND9 Server for DNS distribution. Further components like acme certificate management will be added eventually.
Build images
You can either build the netbox and patroni-postgres containers yourself or let
compose pull them from the repository at git.luethi.co. Note that the reason
i pushed all images to my repo that compose relies on is simply convenience and
reducing load on dockerhub. You can modify compose.yml to source all but the
netbox and patroni-postgres images from dockerhub instead.
# Build all images (bakes in netbox plugins + patroni)
podman-compose --podman-build-args '\-\-network=host' build
Start the Stack via compose
# Start the full stack (etcd + patroni HA + netbox)
podman compose up -d
Building the image directly without compose
# NetBox
podman build \
-f netbox.Containerfile \
--build-arg PYTHON_VERSION=3.14 \
--build-arg NETBOX_VERSION=4.6.5 \
--tag localhost/netbox:4.6.5 \
.
# Patroni-Postgres
podman build \
-f patroni.Containerfile \
--build-arg POSTGRES_VERSION=16 \
--tag localhost/patroni-postgres:16 \
.
Updating NetBox
- Bump
PYTHON_VERSIONandNETBOX_VERSIONin.env - Rebuild:
podman-compose --podman-build-args '\-\-network=host build --no-cache - Restart:
podman-compose up -d
Migrations run automatically on startup via the entrypoint.
PostgreSQL HA with Patroni
PostgreSQL runs as a 3-node Patroni cluster (1 leader + 2 replicas) with etcd as the Distributed Configuration Store (DCS). HAProxy routes write traffic to the current leader via Patroni REST API health checks.
Architecture
┌──────────────┐
│ etcd │ :2379
└──────┬───────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ patroni1 │ │ patroni2 │ │ patroni3 │
│ pg :5432 │ │ pg :5433 │ │ pg :5434 │
│ api :8008 │ │ api :8009 │ │ api :8010 │
│ (leader) │◄───│ (replica) │◄───│ (replica) │
└──────┬──────┘ └─────────────┘ └─────────────┘
│
┌──────▼──────┐
│ HAProxy │ :5000 → leader :7000 → stats
└──────┬──────┘
│
┌──────▼──────┐
│ NetBox │ connects to localhost:5000
└─────────────┘
Ports
| Service | PostgreSQL | Patroni API |
|---|---|---|
| patroni1 | 5432 | 8008 |
| patroni2 | 5433 | 8009 |
| patroni3 | 5434 | 8010 |
HAProxy listens on :5000 (write) and :7000 (stats UI).
Cluster management
# Show cluster topology
podman exec patroni1 patronictl list
# Switchover (planned leader change)
podman exec patroni1 patronictl switchover
# Check cluster health
curl http://localhost:8008/health
curl http://localhost:8008/cluster | jq .
# HAProxy stats UI
open http://localhost:7000
Converting existing standalone PostgreSQL to Patroni
If you have an existing postgres_data volume from the old standalone postgres
service, follow these steps to convert it to a Patroni-managed cluster:
-
Stop the stack:
podman compose down -
Start only the old postgres temporarily to create Patroni users:
podman compose run --rm --no-deps postgres \ psql -U netbox -d netbox -f /init-patroni-users.sql(If the
postgresservice no longer exists in compose, use any postgres 16-alpine container with the same volume mounted.) -
Update pg_hba.conf in the existing data volume to allow replication:
echo 'host replication replicator 0.0.0.0/0 md5' >> \ /var/lib/containers/storage/volumes/netbox_postgres_data/_data/pg_hba.conf -
Start the new Patroni stack:
podman compose up -d- patroni1 detects the existing data and takes over management without re-initializing.
- patroni2 and patroni3 start with empty data dirs and are bootstrapped as
replicas via
pg_basebackupfrom patroni1.
-
Verify:
podman exec patroni1 patronictl listShould show 1 Leader + 2 Replicas in
running/streamingstate.
Fresh start (no existing data)
If you don't need to preserve existing data, simply remove the old volume and
start fresh — Patroni will run initdb on patroni1 and bootstrap replicas:
podman volume rm netbox_postgres_data
podman compose up -d