Files
sven e7b53b6965 modified: .env
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
2026-08-03 15:04:38 +02:00

5.1 KiB

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

  1. Bump PYTHON_VERSION and NETBOX_VERSION in .env
  2. Rebuild: podman-compose --podman-build-args '\-\-network=host build --no-cache
  3. 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:

  1. Stop the stack:

    podman compose down
    
  2. 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 postgres service no longer exists in compose, use any postgres 16-alpine container with the same volume mounted.)

  3. 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
    
  4. 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_basebackup from patroni1.
  5. Verify:

    podman exec patroni1 patronictl list
    

    Should show 1 Leader + 2 Replicas in running / streaming state.

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