new file: .env
new file: Containerfile new file: README.md new file: compose.yml new file: configuration.py new file: entrypoint.sh new file: local_requirements.txt new file: named.conf new file: nginx.conf new file: rndc.key
This commit is contained in:
commit
136ee48052
10 files changed
+593
No files matched your search
@@ -0,0 +1,99 @@
|
||||
# ============================================================
|
||||
# NetBox 4.5 – Python 3.12 / Alpine
|
||||
# Custom validators → baked in via COPY
|
||||
# Custom scripts → mounted as volume at runtime
|
||||
# Plugins & extra deps → baked in via pip
|
||||
# configuration.py → mounted as volume at runtime
|
||||
# ============================================================
|
||||
|
||||
# ── Stage 1: build wheel cache ────────────────────────────────
|
||||
ARG PYTHON_VERSION
|
||||
|
||||
FROM python:${PYTHON_VERSION}-alpine AS builder
|
||||
|
||||
ARG NETBOX_VERSION
|
||||
LABEL org.opencontainers.image.version=$NETBOX_VERSION
|
||||
|
||||
RUN apk update && apk add --no-cache \
|
||||
build-base \
|
||||
cargo \
|
||||
curl \
|
||||
git \
|
||||
libffi-dev \
|
||||
openssl-dev \
|
||||
postgresql-dev \
|
||||
xmlsec-dev \
|
||||
zlib-dev
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Download NetBox source
|
||||
RUN curl -fsSL \
|
||||
"https://github.com/netbox-community/netbox/archive/refs/tags/v${NETBOX_VERSION}.tar.gz" \
|
||||
| tar -xz --strip-components=1
|
||||
|
||||
# Install NetBox core requirements
|
||||
RUN pip install --upgrade pip && \
|
||||
pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
|
||||
|
||||
# ── Install plugins & extra deps here ────────────────────────
|
||||
COPY local_requirements.txt /build/local_requirements.txt
|
||||
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r /build/local_requirements.txt
|
||||
|
||||
# ── Stage 2: runtime image ────────────────────────────────────
|
||||
FROM python:${PYTHON_VERSION}-alpine AS runtime
|
||||
|
||||
LABEL org.opencontainers.image.title="netbox" \
|
||||
org.opencontainers.image.version="${NETBOX_VERSION}" \
|
||||
org.opencontainers.image.description="NetBox network source of truth"
|
||||
|
||||
# Runtime OS deps only (no build tools)
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
curl \
|
||||
libffi \
|
||||
libpq \
|
||||
libxslt \
|
||||
openssl \
|
||||
xmlsec \
|
||||
zlib && \
|
||||
addgroup -S netbox && \
|
||||
adduser -S -G netbox -h /opt/netbox netbox
|
||||
|
||||
WORKDIR /opt/netbox
|
||||
|
||||
# Copy NetBox source from builder
|
||||
COPY --from=builder /build .
|
||||
|
||||
# Install all pre-built wheels (core + plugins)
|
||||
COPY --from=builder /wheels /wheels
|
||||
RUN pip install --no-cache-dir --no-index --find-links=/wheels /wheels/*.whl && \
|
||||
rm -rf /wheels
|
||||
|
||||
# Create directories NetBox expects to own
|
||||
RUN mkdir -p \
|
||||
netbox/media \
|
||||
netbox/reports \
|
||||
netbox/scripts \
|
||||
netbox/static && \
|
||||
chown -R netbox:netbox /opt/netbox
|
||||
|
||||
# ── Volume mount points ───────────────────────────────────────
|
||||
#
|
||||
# /opt/netbox/netbox/netbox/configuration.py
|
||||
# → your configuration.py
|
||||
#
|
||||
# /opt/netbox/netbox/media/
|
||||
# → uploaded media (persist across restarts)
|
||||
#
|
||||
VOLUME ["/opt/netbox/netbox/media"]
|
||||
VOLUME ["/opt/netbox/netbox/static"]
|
||||
|
||||
# Entrypoint script handles: collectstatic, migrate, then gunicorn/worker
|
||||
COPY --chown=netbox:netbox ./entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
USER netbox
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["gunicorn"]
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 netbox image
|
||||
You can either build the netbox container yourself with following command or let
|
||||
compose pull it from my own 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` images from dockerhub instead.
|
||||
|
||||
```bash
|
||||
# Build the image (bakes in plugins)
|
||||
podman-compose --podman-build-args '\-\-network=host' build
|
||||
```
|
||||
|
||||
## Start the Stack via compose
|
||||
```bash
|
||||
# Start the full stack
|
||||
podman compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building the image directly without compose
|
||||
```bash
|
||||
podman build \
|
||||
--build-arg PYTHON_VERSION=3.14 \
|
||||
--build-arg NETBOX_VERSION=4.6.5 \
|
||||
--tag localhost/netbox:4.6.5 \
|
||||
.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
name: netbox
|
||||
|
||||
services:
|
||||
|
||||
# __ PostgreSQL ______________________________________________
|
||||
postgres:
|
||||
image: git.luethi.co/sven/netbox-stack/postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
environment:
|
||||
POSTGRES_DB: netbox
|
||||
POSTGRES_USER: netbox
|
||||
POSTGRES_PASSWORD: netbox
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data:U
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U netbox"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# __ Redis ___________________________________________________
|
||||
redis:
|
||||
image: git.luethi.co/sven/netbox-stack/redis:7-alpine
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
command: redis-server --save 60 1
|
||||
volumes:
|
||||
- redis_data:/data:U
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
# __ NetBox web (Gunicorn) ___________________________________
|
||||
netbox:
|
||||
image: git.luethi.co/sven/netbox-stack/netbox:${NETBOX_VERSION}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Containerfile
|
||||
network: host
|
||||
args:
|
||||
NETBOX_VERSION: ${NETBOX_VERSION}
|
||||
PYTHON_VERSION: ${PYTHON_VERSION}
|
||||
HTTP_PROXY: ${HTTP_PROXY}
|
||||
HTTPS_PROXY: ${HTTPS_PROXY}
|
||||
NO_PROXY: ${NO_PROXY}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./configuration.py:/opt/netbox/netbox/netbox/configuration.py:ro
|
||||
- netbox_custom_scripts:/opt/netbox/netbox/scripts:ro
|
||||
- netbox_media:/opt/netbox/netbox/media:U
|
||||
- netbox_static:/opt/netbox/netbox/static:U
|
||||
environment:
|
||||
GUNICORN_BIND: "localhost:8085"
|
||||
GUNICORN_WORKERS: 4
|
||||
GUNICORN_THREADS: 3
|
||||
GUNICORN_TIMEOUT: 120
|
||||
GUNICORN_MAX_REQUESTS: 5000
|
||||
GUNICORN_MAX_REQUESTS_JITTER: 500
|
||||
command: gunicorn
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "/opt/netbox/netbox/manage.py", "check"]
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 5
|
||||
|
||||
|
||||
# __ NetBox RQ worker ________________________________________
|
||||
netbox-worker:
|
||||
image: git.luethi.co/sven/netbox-stack/netbox:${NETBOX_VERSION}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
- netbox
|
||||
volumes:
|
||||
- ./configuration.py:/opt/netbox/netbox/netbox/configuration.py:ro
|
||||
- netbox_custom_scripts:/opt/netbox/netbox/scripts:ro
|
||||
- netbox_media:/opt/netbox/netbox/media:U
|
||||
- netbox_static:/opt/netbox/netbox/static:U
|
||||
command: worker
|
||||
|
||||
# __ NetBox DNS transfer endpoint ____________________________
|
||||
netbox-dns-transfer-endpoint:
|
||||
image: git.luethi.co/sven/netbox-stack/netbox:${NETBOX_VERSION}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
- netbox
|
||||
volumes:
|
||||
- ./configuration.py:/opt/netbox/netbox/netbox/configuration.py:ro
|
||||
command: dns-transfer-endpoint
|
||||
|
||||
# __ NGINX reverse proxy _____________________________________
|
||||
nginx:
|
||||
image: git.luethi.co/sven/netbox-stack/nginx:alpine
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
- netbox
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- netbox_static:/static:ro
|
||||
|
||||
# __ BIND9 ___________________________________________________
|
||||
bind9:
|
||||
image: git.luethi.co/sven/netbox-stack/bind9:9.20
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- netbox-dns-transfer-endpoint
|
||||
network_mode: host
|
||||
volumes:
|
||||
- ./named.conf:/etc/bind/named.conf:ro
|
||||
- ./rndc.key:/etc/bind/rndc.key:ro
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
netbox_media:
|
||||
netbox_static:
|
||||
netbox_custom_scripts:
|
||||
@@ -0,0 +1,115 @@
|
||||
# ============================================================
|
||||
# configuration.py – NetBox local configuration
|
||||
# Mount this file into the container at:
|
||||
# /opt/netbox/netbox/netbox/configuration.py
|
||||
# ============================================================
|
||||
|
||||
# ── Required settings ─────────────────────────────────────────
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"*",
|
||||
]
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"http://localhost:8084",
|
||||
"http://127.0.0.1:8084",
|
||||
"http://[::1]:8084"
|
||||
]
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
DATABASE = {
|
||||
"NAME": "netbox",
|
||||
"USER": "netbox",
|
||||
"PASSWORD": "netbox",
|
||||
"HOST": "localhost",
|
||||
"PORT": "5432",
|
||||
"CONN_MAX_AGE": 300,
|
||||
}
|
||||
|
||||
REDIS = {
|
||||
"tasks": {
|
||||
"HOST": "localhost", # service name in your pod / compose
|
||||
"PORT": 6379,
|
||||
"PASSWORD": "",
|
||||
"DATABASE": 0,
|
||||
"SSL": False,
|
||||
},
|
||||
"caching": {
|
||||
"HOST": "localhost",
|
||||
"PORT": 6379,
|
||||
"PASSWORD": "",
|
||||
"DATABASE": 1,
|
||||
"SSL": False,
|
||||
},
|
||||
}
|
||||
|
||||
SECRET_KEY = "CuUxP6Vg5(f^#Tk+QRgZ)Wt9x1NG##yv(c7BSmbZ+4v@Sd(ha0"
|
||||
|
||||
PLUGINS = [
|
||||
"netbox_dns",
|
||||
"netbox_dns_bridge"
|
||||
]
|
||||
|
||||
# Plugin-specific configuration (optional)
|
||||
PLUGINS_CONFIG = {
|
||||
"netbox_dns_bridge": {
|
||||
"notify_clients": True,
|
||||
"tsig_keys": {
|
||||
"_default_": {
|
||||
"keyname": "_default_",
|
||||
"algorithm": "hmac-sha256",
|
||||
"secret": "yJrOo4c3rDnv3mIrNDfSpGXePsypavsUSpNGxSojcYM=",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# ── Custom validators ─────────────────────────────────────────
|
||||
# Reference Python classes mounted via volume into the container.
|
||||
# The classes must be importable (i.e. their directory must be on
|
||||
# PYTHONPATH or placed inside netbox/netbox/).
|
||||
|
||||
CUSTOM_VALIDATORS = {
|
||||
# "dcim.site": ["my_validators.SiteValidator"],
|
||||
}
|
||||
|
||||
# ── Optional / commonly changed settings ──────────────────────
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
DATE_FORMAT = "N j, Y"
|
||||
TIME_FORMAT = "g:i a"
|
||||
DATETIME_FORMAT = "N j, Y g:i a"
|
||||
|
||||
# Email
|
||||
#EMAIL = {
|
||||
# "SERVER": "localhost",
|
||||
# "PORT": 25,
|
||||
# "USERNAME": "",
|
||||
# "PASSWORD": "",
|
||||
# "USE_SSL": False,
|
||||
# "USE_TLS": False,
|
||||
# "TIMEOUT": 10,
|
||||
# "FROM_EMAIL": "netbox@example.com",
|
||||
#}
|
||||
|
||||
# Media & static files are stored inside the container (media volume)
|
||||
MEDIA_ROOT = "/opt/netbox/netbox/media"
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "DEBUG",
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
#"level": "WARNING",
|
||||
"level": "DEBUG",
|
||||
},
|
||||
}
|
||||
|
||||
API_TOKEN_PEPPERS = {1: '2QLPiNVBUPaZtvYrVNdRAdqPVBFeM+zkHcOExnBVG6O53xK2FBoewbVRYvS+ygsrMwB69j5qulkl'}
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# /entrypoint.sh – NetBox container entrypoint
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
|
||||
NETBOX_ROOT=/opt/netbox/netbox
|
||||
MANAGE="${NETBOX_ROOT}/manage.py"
|
||||
|
||||
#RETRIES=10
|
||||
wait_for_db() {
|
||||
#until python "${MANAGE}" migrate --check &>/dev/null; do
|
||||
until python "${MANAGE}" check --database default &>/dev/null; do
|
||||
echo "[entrypoint] Waiting for database …"
|
||||
# if (( RETRIES == 0 )); then
|
||||
# echo "[entrypoint] Database did not become ready."
|
||||
# exit 1
|
||||
# fi
|
||||
# ((RETRIES--))
|
||||
sleep 2
|
||||
done
|
||||
echo "[entrypoint] Database is ready."
|
||||
}
|
||||
|
||||
run_migrations() {
|
||||
echo "[entrypoint] Applying database migrations …"
|
||||
python "${MANAGE}" migrate --no-input
|
||||
}
|
||||
|
||||
run_collectstatic() {
|
||||
echo "[entrypoint] Collecting static files …"
|
||||
python "${MANAGE}" collectstatic --no-input --clear
|
||||
}
|
||||
|
||||
case "${1:-gunicorn}" in
|
||||
gunicorn)
|
||||
wait_for_db
|
||||
run_migrations
|
||||
run_collectstatic
|
||||
echo "[entrypoint] Starting Gunicorn …"
|
||||
cd $NETBOX_ROOT
|
||||
exec gunicorn \
|
||||
--bind "${GUNICORN_BIND:-localhost:8080}" \
|
||||
--workers "${GUNICORN_WORKERS:-4}" \
|
||||
--worker-class "${GUNICORN_WORKER_CLASS:-gthread}" \
|
||||
--threads "${GUNICORN_THREADS:-3}" \
|
||||
--timeout "${GUNICORN_TIMEOUT:-120}" \
|
||||
--max-requests "${GUNICORN_MAX_REQUESTS:-5000}" \
|
||||
--max-requests-jitter "${GUNICORN_MAX_REQUESTS_JITTER:-500}" \
|
||||
--log-level "${GUNICORN_LOG_LEVEL:-info}" \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
netbox.wsgi
|
||||
;;
|
||||
|
||||
worker)
|
||||
wait_for_db
|
||||
echo "[entrypoint] Starting RQ worker …"
|
||||
exec python "${MANAGE}" rqworker \
|
||||
high default low
|
||||
;;
|
||||
|
||||
dns-transfer-endpoint)
|
||||
wait_for_db
|
||||
echo "[entrypoint] Starting DNS Transfer Endpoint …"
|
||||
exec python "${MANAGE}" dns-transfer-endpoint --address ::1
|
||||
;;
|
||||
|
||||
housekeeping)
|
||||
wait_for_db
|
||||
echo "[entrypoint] Running housekeeping …"
|
||||
exec python "${MANAGE}" housekeeping
|
||||
;;
|
||||
|
||||
shell)
|
||||
exec python "${MANAGE}" shell
|
||||
;;
|
||||
|
||||
*)
|
||||
# Pass-through for arbitrary commands (e.g. manage.py subcommands)
|
||||
exec python "${MANAGE}" "$@"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,2 @@
|
||||
netbox-plugin-dns==1.5.10
|
||||
netbox-plugin-dns-bridge==1.5.7
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
options {
|
||||
listen-on port 5355 { any; };
|
||||
listen-on-v6 port 5355 { any; };
|
||||
directory "/var/cache/bind";
|
||||
recursion no;
|
||||
allow-recursion { none; };
|
||||
allow-query { any; };
|
||||
};
|
||||
|
||||
logging {
|
||||
channel stderr_log {
|
||||
stderr;
|
||||
severity dynamic;
|
||||
print-time yes;
|
||||
print-category yes;
|
||||
print-severity yes;
|
||||
};
|
||||
|
||||
category default { stderr_log; };
|
||||
category general { stderr_log; };
|
||||
category queries { stderr_log; };
|
||||
category resolver { stderr_log; };
|
||||
category client { stderr_log; };
|
||||
category security { stderr_log; };
|
||||
category config { stderr_log; };
|
||||
category dnssec { stderr_log; };
|
||||
category lame-servers { stderr_log; };
|
||||
category network { stderr_log; };
|
||||
category notify { stderr_log; };
|
||||
category xfer-in { stderr_log; };
|
||||
category xfer-out { stderr_log; };
|
||||
category update { stderr_log; };
|
||||
category update-security { stderr_log; };
|
||||
category dispatch { stderr_log; };
|
||||
category rpz { stderr_log; };
|
||||
category rate-limit { stderr_log; };
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 127.0.0.1 port 9953
|
||||
allow { 127.0.0.1; } keys { "rndc-key"; };
|
||||
|
||||
inet ::1 port 9953
|
||||
allow { ::1; } keys { "rndc-key"; };
|
||||
};
|
||||
|
||||
########## ACLs ##########
|
||||
|
||||
acl public {
|
||||
!10.0.0.0/8;
|
||||
!172.16.0.0/12;
|
||||
!192.168.0.0/16;
|
||||
any;
|
||||
};
|
||||
|
||||
acl private {
|
||||
10.0.0.0/8;
|
||||
172.16.0.0/12;
|
||||
192.168.0.0/16;
|
||||
};
|
||||
|
||||
######## TSIG Keys ########
|
||||
key "rndc-key" {
|
||||
algorithm hmac-sha256;
|
||||
secret "yJrOo4c3rDnv3mIrNDfSpGXePsypavsUSpNGxSojcYM=";
|
||||
};
|
||||
|
||||
key "_default_" {
|
||||
algorithm hmac-sha256;
|
||||
secret "yJrOo4c3rDnv3mIrNDfSpGXePsypavsUSpNGxSojcYM=";
|
||||
};
|
||||
|
||||
###########################
|
||||
|
||||
|
||||
########## ZONES ##########
|
||||
view "_default_" {
|
||||
match-clients { key "_default_"; public; };
|
||||
allow-notify { key "_default_"; };
|
||||
allow-transfer { key "_default_"; };
|
||||
|
||||
catalog-zones {
|
||||
zone "catz"
|
||||
default-masters { ::1 port 5354 key "_default_"; }
|
||||
zone-directory "/var/lib/bind"
|
||||
min-update-interval 1;
|
||||
};
|
||||
|
||||
zone "catz" {
|
||||
type slave;
|
||||
file "/var/lib/bind/catz_default";
|
||||
masters { ::1 port 5354 key "_default_"; };
|
||||
notify no;
|
||||
};
|
||||
};
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
server {
|
||||
listen 8084;
|
||||
server_name _;
|
||||
|
||||
location /static/ {
|
||||
alias /static/;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8085;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user