Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad4bb8750d | ||
|
|
f1e00b946f | ||
|
|
7ee7d0c961 | ||
|
|
71e769c002 | ||
|
|
16dd67c2c0 | ||
|
|
c536a16a2a |
No files matched your search
@@ -0,0 +1,32 @@
|
||||
POSTGRES_DB=netbox
|
||||
POSTGRES_USER=netbox
|
||||
POSTGRES_PASSWORD=CHANGE_ME_POSTGRES_PASSWORD
|
||||
|
||||
DB_NAME=netbox
|
||||
DB_USER=netbox
|
||||
DB_PASSWORD=CHANGE_ME_POSTGRES_PASSWORD
|
||||
DB_HOST=postgres
|
||||
DB_PORT=5432
|
||||
DB_SSLMODE=disable
|
||||
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD
|
||||
REDIS_DATABASE=0
|
||||
REDIS_CACHE_HOST=redis-cache
|
||||
REDIS_CACHE_PORT=6379
|
||||
REDIS_CACHE_PASSWORD=CHANGE_ME_REDIS_CACHE_PASSWORD
|
||||
REDIS_CACHE_DATABASE=1
|
||||
|
||||
ALLOWED_HOSTS=localhost,127.0.0.1,[::1],netbox.forust.xyz,netbox.workstation.internal
|
||||
CSRF_TRUSTED_ORIGINS=https://netbox.forust.xyz,https://netbox.workstation.internal
|
||||
|
||||
SECRET_KEY=CHANGE_ME_DJANGO_SECRET_KEY
|
||||
API_TOKEN_PEPPER_1=CHANGE_ME_API_TOKEN_PEPPER
|
||||
TIME_ZONE=Europe/Bratislava
|
||||
TZ=Europe/Bratislava
|
||||
|
||||
SKIP_SUPERUSER=false
|
||||
SUPERUSER_NAME=admin
|
||||
SUPERUSER_EMAIL=admin@example.com
|
||||
SUPERUSER_PASSWORD=CHANGE_ME_SUPERUSER_PASSWORD
|
||||
@@ -0,0 +1,96 @@
|
||||
# NetBox
|
||||
|
||||
NetBox for homelab documentation and visualization. Two runtimes are available:
|
||||
|
||||
| Runtime | Manifest | Purpose |
|
||||
| ------- | -------------- | -------------------------------------------------------------- |
|
||||
| Docker | `compose.yaml` | Local stand on `127.0.0.1:8000` (no public exposure) |
|
||||
| k8s | `k8s/` | Homelab service on `netbox.forust.xyz` (and the internal name) |
|
||||
|
||||
Both use the same image (`netboxcommunity/netbox:v4.7-5.1.1`) and Valkey for tasks
|
||||
plus a second logical database for caching. The Docker stand keeps its own
|
||||
PostgreSQL container, while the k8s deployment uses the shared `database` cluster
|
||||
(`postgres.database.svc.cluster.local:5432`, role/database `netbox`); only Valkey
|
||||
stays a per-service StatefulSet.
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# replace CHANGE_ME
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The UI is available at <http://localhost:8000>. The port is bound to `127.0.0.1`
|
||||
intentionally, so this stand is not exposed on the LAN or public interfaces.
|
||||
|
||||
The `netbox` service is also attached to the external `proxy` network and carries
|
||||
Traefik labels for `netbox.forust.xyz` and `netbox.workstation.internal`. Those
|
||||
labels only take effect while the Docker Traefik stack is running; it is currently
|
||||
stopped, and the live ingress path in this homelab is the k8s Traefik.
|
||||
|
||||
Inspect startup and health with:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs -f netbox
|
||||
```
|
||||
|
||||
Stop it with `docker compose down`; data is kept in the named volumes
|
||||
`netbox-postgres`, `netbox-media-files`, `netbox-reports-files`,
|
||||
`netbox-scripts-files` and `netbox-redis-data`.
|
||||
|
||||
## Kubernetes
|
||||
|
||||
`k8s/` is deployed in the homelab cluster and serves `netbox.forust.xyz` publicly
|
||||
plus `netbox.workstation.internal` / `netbox.gigaforust.internal` internally. To
|
||||
rebuild it from scratch:
|
||||
|
||||
```bash
|
||||
# 1. shared PostgreSQL: the password lives in the shared secret, NetBox keeps a copy
|
||||
kubectl -n database patch secret postgres-shared-secrets \
|
||||
--type merge -p '{"stringData":{"NETBOX_DB_PASSWORD":"<same value>"}}'
|
||||
kubectl -n database exec postgres17-0 -- psql -U postgres -d postgres \
|
||||
-c 'CREATE ROLE netbox LOGIN PASSWORD ...' -c 'CREATE DATABASE netbox OWNER netbox'
|
||||
|
||||
# 2. secrets first: the deploy workflow never applies *secret*.yaml
|
||||
cp k8s/secrets.yaml.example k8s/secrets.yaml # replace CHANGE_ME
|
||||
kubectl apply -f k8s/secrets.yaml
|
||||
|
||||
# 3. manifests
|
||||
kubectl apply -f k8s/
|
||||
```
|
||||
|
||||
The shared cluster is reached at `postgres.database.svc.cluster.local:5432`. Its
|
||||
NetworkPolicy (`postgres/k8s/network-policy.yaml`) must list the `netbox` namespace
|
||||
or connections are dropped, and `postgres/initdb/01-create-databases.sh` already
|
||||
creates the role and database on a fresh data directory. NetBox has no PostgreSQL
|
||||
StatefulSet of its own — only `netbox-valkey`.
|
||||
|
||||
`netbox.forust.xyz` resolves to this host (`78.98.72.122`) through the `DOMAINS`
|
||||
list in the `default/cfddns` secret. cert-manager issues `netbox-prod-tls` with the
|
||||
`letsencrypt-prod` issuer, the internal route uses `internal-wildcard-tls`.
|
||||
|
||||
Resources are permanent again now that the first-boot migrations are complete:
|
||||
the web container reserves `100m`/`512Mi` and is capped at `2` CPU/`2Gi`, the
|
||||
worker reserves `50m`/`256Mi` and is capped at `1` CPU/`1Gi`, and Valkey reserves
|
||||
`25m`/`64Mi` and is capped at `250m`/`256Mi`. The deliberately generous CPU caps
|
||||
leave enough headroom for future schema migrations without letting one process
|
||||
consume the whole node.
|
||||
|
||||
The first start applies ~810 migrations, each in its own transaction with DDL and
|
||||
a commit; every later start is a no-op. The startup probe allows 15 minutes and
|
||||
`progressDeadlineSeconds` is 1800 for the same reason. Probes run inside the pod
|
||||
and explicitly set `Host: netbox.forust.xyz`; a kubelet `httpGet.host` field would
|
||||
replace the probe destination with that public hostname and bypass the pod.
|
||||
|
||||
## Secrets
|
||||
|
||||
- `netbox/.env` (compose) and `netbox/k8s/secrets.yaml` (k8s) are gitignored. Only
|
||||
`.env.example` and `k8s/secrets.yaml.example` are committed.
|
||||
- `netbox/configuration/configuration.py` is env-driven: hosts, database, Redis and
|
||||
the Django keys all come from the environment, so the same settings file works in
|
||||
both runtimes. The k8s copy lives in the `netbox-settings` ConfigMap
|
||||
(`k8s/settings.yaml`) and must be kept in sync with the file.
|
||||
- Rotating `SECRET_KEY` invalidates all sessions; rotating `API_TOKEN_PEPPER_1`
|
||||
invalidates every API token.
|
||||
File renamed without changes.
@@ -0,0 +1,137 @@
|
||||
services:
|
||||
netbox:
|
||||
image: docker.io/netboxcommunity/netbox:v4.7-5.1.1
|
||||
container_name: netbox
|
||||
restart: unless-stopped
|
||||
user: "netbox:root"
|
||||
ports:
|
||||
- "127.0.0.1:8000:8080"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
GRANIAN_WORKERS: "2"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
redis-cache:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./configuration:/etc/netbox/config:z,ro
|
||||
- netbox-media-files:/opt/netbox/netbox/media
|
||||
- netbox-reports-files:/opt/netbox/netbox/reports
|
||||
- netbox-scripts-files:/opt/netbox/netbox/scripts
|
||||
networks:
|
||||
- default
|
||||
- proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.netbox.loadbalancer.server.port=8080"
|
||||
|
||||
# Prod Router
|
||||
- "traefik.http.routers.netbox.rule=Host(`netbox.forust.xyz`)"
|
||||
- "traefik.http.routers.netbox.entrypoints=websecure"
|
||||
- "traefik.http.routers.netbox.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.netbox.tls.certresolver=letsencrypt"
|
||||
# Local Router
|
||||
- "traefik.http.routers.netbox-local.rule=Host(`netbox.workstation.internal`)"
|
||||
- "traefik.http.routers.netbox-local.entrypoints=websecure"
|
||||
- "traefik.http.routers.netbox-local.tls=true"
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/netbox/health.sh"]
|
||||
start_period: 600s
|
||||
timeout: 5s
|
||||
interval: 15s
|
||||
retries: 10
|
||||
|
||||
netbox-worker:
|
||||
image: docker.io/netboxcommunity/netbox:v4.7-5.1.1
|
||||
container_name: netbox-worker
|
||||
restart: unless-stopped
|
||||
user: "netbox:root"
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- /opt/netbox/netbox/manage.py
|
||||
- rqworker
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
netbox:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./configuration:/etc/netbox/config:z,ro
|
||||
- netbox-media-files:/opt/netbox/netbox/media
|
||||
- netbox-reports-files:/opt/netbox/netbox/reports
|
||||
- netbox-scripts-files:/opt/netbox/netbox/scripts
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ps -ef | grep -q '[r]qworker'"]
|
||||
start_period: 30s
|
||||
timeout: 5s
|
||||
interval: 15s
|
||||
retries: 10
|
||||
|
||||
postgres:
|
||||
image: docker.io/postgres:18.6-alpine
|
||||
container_name: netbox-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: "${POSTGRES_DB:?POSTGRES_DB must be set}"
|
||||
POSTGRES_USER: "${POSTGRES_USER:?POSTGRES_USER must be set}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}"
|
||||
volumes:
|
||||
- netbox-postgres:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", 'pg_isready -q -t 2 -d "$${POSTGRES_DB}" -U "$${POSTGRES_USER}"']
|
||||
start_period: 20s
|
||||
timeout: 5s
|
||||
interval: 10s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: docker.io/valkey/valkey:9.1.2-alpine
|
||||
container_name: netbox-redis
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- valkey-server --appendonly yes --requirepass "$$REDIS_PASSWORD"
|
||||
environment:
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD:?REDIS_PASSWORD must be set}"
|
||||
volumes:
|
||||
- netbox-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", 'valkey-cli --pass "$${REDIS_PASSWORD}" ping | grep -q PONG']
|
||||
start_period: 5s
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
|
||||
redis-cache:
|
||||
image: docker.io/valkey/valkey:9.1.2-alpine
|
||||
container_name: netbox-redis-cache
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- valkey-server --requirepass "$$REDIS_CACHE_PASSWORD"
|
||||
environment:
|
||||
REDIS_CACHE_PASSWORD: "${REDIS_CACHE_PASSWORD:?REDIS_CACHE_PASSWORD must be set}"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", 'valkey-cli --pass "$${REDIS_CACHE_PASSWORD}" ping | grep -q PONG']
|
||||
start_period: 5s
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
netbox-media-files:
|
||||
netbox-reports-files:
|
||||
netbox-scripts-files:
|
||||
netbox-postgres:
|
||||
netbox-redis-data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
proxy:
|
||||
external: true
|
||||
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
|
||||
|
||||
def _csv(name, default=""):
|
||||
return [item.strip() for item in os.environ.get(name, default).split(",") if item.strip()]
|
||||
|
||||
|
||||
ALLOWED_HOSTS = _csv("ALLOWED_HOSTS", "localhost,127.0.0.1,[::1]")
|
||||
CSRF_TRUSTED_ORIGINS = _csv("CSRF_TRUSTED_ORIGINS")
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"NAME": os.environ["DB_NAME"],
|
||||
"USER": os.environ["DB_USER"],
|
||||
"PASSWORD": os.environ["DB_PASSWORD"],
|
||||
"HOST": os.environ["DB_HOST"],
|
||||
"PORT": os.environ.get("DB_PORT", "5432"),
|
||||
"OPTIONS": {"sslmode": os.environ.get("DB_SSLMODE", "disable")},
|
||||
"CONN_MAX_AGE": int(os.environ.get("DB_CONN_MAX_AGE", "300")),
|
||||
}
|
||||
}
|
||||
|
||||
REDIS = {
|
||||
"tasks": {
|
||||
"HOST": os.environ["REDIS_HOST"],
|
||||
"PORT": int(os.environ.get("REDIS_PORT", "6379")),
|
||||
"PASSWORD": os.environ["REDIS_PASSWORD"],
|
||||
"DATABASE": int(os.environ.get("REDIS_DATABASE", "0")),
|
||||
"SSL": False,
|
||||
},
|
||||
"caching": {
|
||||
"HOST": os.environ["REDIS_CACHE_HOST"],
|
||||
"PORT": int(os.environ.get("REDIS_CACHE_PORT", "6379")),
|
||||
"PASSWORD": os.environ["REDIS_CACHE_PASSWORD"],
|
||||
"DATABASE": int(os.environ.get("REDIS_CACHE_DATABASE", "1")),
|
||||
"SSL": False,
|
||||
},
|
||||
}
|
||||
|
||||
SECRET_KEY = os.environ["SECRET_KEY"]
|
||||
API_TOKEN_PEPPERS = {1: os.environ["API_TOKEN_PEPPER_1"]}
|
||||
TIME_ZONE = os.environ.get("TIME_ZONE", "UTC")
|
||||
MEDIA_ROOT = "/opt/netbox/netbox/media"
|
||||
REPORTS_ROOT = "/opt/netbox/netbox/reports"
|
||||
SCRIPTS_ROOT = "/opt/netbox/netbox/scripts"
|
||||
CENSUS_REPORTING_ENABLED = False
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: netbox-prod-tls
|
||||
namespace: netbox
|
||||
spec:
|
||||
secretName: netbox-prod-tls
|
||||
dnsNames:
|
||||
- netbox.forust.xyz
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: internal-wildcard-tls
|
||||
namespace: netbox
|
||||
spec:
|
||||
secretName: internal-wildcard-tls
|
||||
dnsNames:
|
||||
- "*.workstation.internal"
|
||||
- "*.gigaforust.internal"
|
||||
- workstation.internal
|
||||
- gigaforust.internal
|
||||
issuerRef:
|
||||
name: internal-ca
|
||||
kind: ClusterIssuer
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: netbox-config
|
||||
namespace: netbox
|
||||
data:
|
||||
DB_HOST: "postgres.database.svc.cluster.local"
|
||||
DB_PORT: "5432"
|
||||
DB_SSLMODE: "disable"
|
||||
REDIS_HOST: "netbox-valkey"
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_DATABASE: "0"
|
||||
REDIS_CACHE_HOST: "netbox-valkey"
|
||||
REDIS_CACHE_PORT: "6379"
|
||||
REDIS_CACHE_DATABASE: "1"
|
||||
TIME_ZONE: "Europe/Bratislava"
|
||||
TZ: "Europe/Bratislava"
|
||||
GRANIAN_WORKERS: "2"
|
||||
ALLOWED_HOSTS: "netbox.forust.xyz,netbox.workstation.internal,netbox.gigaforust.internal"
|
||||
CSRF_TRUSTED_ORIGINS: "https://netbox.forust.xyz,https://netbox.workstation.internal,https://netbox.gigaforust.internal"
|
||||
SKIP_SUPERUSER: "false"
|
||||
@@ -0,0 +1,36 @@
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: netbox-prod
|
||||
namespace: netbox
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`netbox.forust.xyz`)
|
||||
kind: Rule
|
||||
middlewares:
|
||||
- name: crowdsec-bouncer
|
||||
namespace: crowdsec
|
||||
services:
|
||||
- name: netbox-service
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: netbox-prod-tls
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: netbox-local
|
||||
namespace: netbox
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`netbox.workstation.internal`) || Host(`netbox.gigaforust.internal`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: netbox-service
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: internal-wildcard-tls
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: netbox
|
||||
@@ -0,0 +1,204 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: netbox-service
|
||||
namespace: netbox
|
||||
spec:
|
||||
selector:
|
||||
app: netbox
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: netbox-deployment
|
||||
namespace: netbox
|
||||
labels:
|
||||
app: netbox
|
||||
spec:
|
||||
replicas: 1
|
||||
progressDeadlineSeconds: 300
|
||||
selector:
|
||||
matchLabels:
|
||||
app: netbox
|
||||
strategy:
|
||||
# ReadWriteOnce PVC
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: netbox
|
||||
spec:
|
||||
containers:
|
||||
- name: netbox
|
||||
image: docker.io/netboxcommunity/netbox:v4.7-5.1.1
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: netbox-config
|
||||
- secretRef:
|
||||
name: netbox-secrets
|
||||
volumeMounts:
|
||||
- name: netbox-config
|
||||
mountPath: /etc/netbox/config
|
||||
readOnly: true
|
||||
- name: netbox-media
|
||||
mountPath: /opt/netbox/netbox/media
|
||||
- name: netbox-reports
|
||||
mountPath: /opt/netbox/netbox/reports
|
||||
- name: netbox-scripts
|
||||
mountPath: /opt/netbox/netbox/scripts
|
||||
startupProbe:
|
||||
exec:
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- -c
|
||||
- >-
|
||||
exec /usr/bin/curl --fail --silent --show-error --max-time 4
|
||||
--header 'Host: netbox.forust.xyz'
|
||||
http://127.0.0.1:8080/login/ >/dev/null
|
||||
failureThreshold: 90
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- -c
|
||||
- >-
|
||||
exec /usr/bin/curl --fail --silent --show-error --max-time 4
|
||||
--header 'Host: netbox.forust.xyz'
|
||||
http://127.0.0.1:8080/login/ >/dev/null
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- -c
|
||||
- >-
|
||||
exec /usr/bin/curl --fail --silent --show-error --max-time 4
|
||||
--header 'Host: netbox.forust.xyz'
|
||||
http://127.0.0.1:8080/login/ >/dev/null
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
volumes:
|
||||
- name: netbox-config
|
||||
configMap:
|
||||
name: netbox-settings
|
||||
- name: netbox-media
|
||||
persistentVolumeClaim:
|
||||
claimName: netbox-media-pvc
|
||||
- name: netbox-reports
|
||||
persistentVolumeClaim:
|
||||
claimName: netbox-reports-pvc
|
||||
- name: netbox-scripts
|
||||
persistentVolumeClaim:
|
||||
claimName: netbox-scripts-pvc
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: netbox-worker-deployment
|
||||
namespace: netbox
|
||||
labels:
|
||||
app: netbox-worker
|
||||
spec:
|
||||
replicas: 1
|
||||
progressDeadlineSeconds: 300
|
||||
selector:
|
||||
matchLabels:
|
||||
app: netbox-worker
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: netbox-worker
|
||||
spec:
|
||||
containers:
|
||||
- name: netbox-worker
|
||||
image: docker.io/netboxcommunity/netbox:v4.7-5.1.1
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- netbox/manage.py
|
||||
- rqworker
|
||||
workingDir: /opt/netbox
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: netbox-config
|
||||
- secretRef:
|
||||
name: netbox-secrets
|
||||
volumeMounts:
|
||||
- name: netbox-config
|
||||
mountPath: /etc/netbox/config
|
||||
readOnly: true
|
||||
- name: netbox-media
|
||||
mountPath: /opt/netbox/netbox/media
|
||||
- name: netbox-reports
|
||||
mountPath: /opt/netbox/netbox/reports
|
||||
- name: netbox-scripts
|
||||
mountPath: /opt/netbox/netbox/scripts
|
||||
resources:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "1Gi"
|
||||
volumes:
|
||||
- name: netbox-config
|
||||
configMap:
|
||||
name: netbox-settings
|
||||
- name: netbox-media
|
||||
persistentVolumeClaim:
|
||||
claimName: netbox-media-pvc
|
||||
- name: netbox-reports
|
||||
persistentVolumeClaim:
|
||||
claimName: netbox-reports-pvc
|
||||
- name: netbox-scripts
|
||||
persistentVolumeClaim:
|
||||
claimName: netbox-scripts-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: netbox-media-pvc
|
||||
namespace: netbox
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: netbox-reports-pvc
|
||||
namespace: netbox
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: netbox-scripts-pvc
|
||||
namespace: netbox
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: netbox-secrets
|
||||
namespace: netbox
|
||||
type: Opaque
|
||||
stringData:
|
||||
DB_NAME: "netbox"
|
||||
DB_USER: "netbox"
|
||||
DB_PASSWORD: "CHANGE_ME_POSTGRES_PASSWORD"
|
||||
REDIS_PASSWORD: "CHANGE_ME_VALKEY_PASSWORD"
|
||||
REDIS_CACHE_PASSWORD: "CHANGE_ME_VALKEY_PASSWORD"
|
||||
VALKEY_PASSWORD: "CHANGE_ME_VALKEY_PASSWORD"
|
||||
SECRET_KEY: "CHANGE_ME_DJANGO_SECRET_KEY"
|
||||
API_TOKEN_PEPPER_1: "CHANGE_ME_API_TOKEN_PEPPER"
|
||||
SUPERUSER_NAME: "admin"
|
||||
SUPERUSER_EMAIL: "admin@example.com"
|
||||
SUPERUSER_PASSWORD: "CHANGE_ME_SUPERUSER_PASSWORD"
|
||||
@@ -0,0 +1,56 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: netbox-settings
|
||||
namespace: netbox
|
||||
data:
|
||||
# Sync wit netbox/configuration/configuration.py (the Docker mounts that file).
|
||||
configuration.py: |
|
||||
import os
|
||||
|
||||
|
||||
def _csv(name, default=""):
|
||||
return [item.strip() for item in os.environ.get(name, default).split(",") if item.strip()]
|
||||
|
||||
|
||||
ALLOWED_HOSTS = _csv("ALLOWED_HOSTS", "localhost,127.0.0.1,[::1]")
|
||||
CSRF_TRUSTED_ORIGINS = _csv("CSRF_TRUSTED_ORIGINS")
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"NAME": os.environ["DB_NAME"],
|
||||
"USER": os.environ["DB_USER"],
|
||||
"PASSWORD": os.environ["DB_PASSWORD"],
|
||||
"HOST": os.environ["DB_HOST"],
|
||||
"PORT": os.environ.get("DB_PORT", "5432"),
|
||||
"OPTIONS": {"sslmode": os.environ.get("DB_SSLMODE", "disable")},
|
||||
"CONN_MAX_AGE": int(os.environ.get("DB_CONN_MAX_AGE", "300")),
|
||||
}
|
||||
}
|
||||
|
||||
REDIS = {
|
||||
"tasks": {
|
||||
"HOST": os.environ["REDIS_HOST"],
|
||||
"PORT": int(os.environ.get("REDIS_PORT", "6379")),
|
||||
"PASSWORD": os.environ["REDIS_PASSWORD"],
|
||||
"DATABASE": int(os.environ.get("REDIS_DATABASE", "0")),
|
||||
"SSL": False,
|
||||
},
|
||||
"caching": {
|
||||
"HOST": os.environ["REDIS_CACHE_HOST"],
|
||||
"PORT": int(os.environ.get("REDIS_CACHE_PORT", "6379")),
|
||||
"PASSWORD": os.environ["REDIS_CACHE_PASSWORD"],
|
||||
"DATABASE": int(os.environ.get("REDIS_CACHE_DATABASE", "1")),
|
||||
"SSL": False,
|
||||
},
|
||||
}
|
||||
|
||||
SECRET_KEY = os.environ["SECRET_KEY"]
|
||||
API_TOKEN_PEPPERS = {1: os.environ["API_TOKEN_PEPPER_1"]}
|
||||
TIME_ZONE = os.environ.get("TIME_ZONE", "UTC")
|
||||
MEDIA_ROOT = "/opt/netbox/netbox/media"
|
||||
REPORTS_ROOT = "/opt/netbox/netbox/reports"
|
||||
SCRIPTS_ROOT = "/opt/netbox/netbox/scripts"
|
||||
CENSUS_REPORTING_ENABLED = False
|
||||
@@ -0,0 +1,82 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: netbox-valkey
|
||||
namespace: netbox
|
||||
labels:
|
||||
app: netbox-valkey
|
||||
spec:
|
||||
clusterIP: None
|
||||
selector:
|
||||
app: netbox-valkey
|
||||
ports:
|
||||
- name: valkey
|
||||
port: 6379
|
||||
targetPort: valkey
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: netbox-valkey
|
||||
namespace: netbox
|
||||
labels:
|
||||
app: netbox-valkey
|
||||
spec:
|
||||
serviceName: netbox-valkey
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: netbox-valkey
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: netbox-valkey
|
||||
spec:
|
||||
containers:
|
||||
- name: valkey
|
||||
image: docker.io/valkey/valkey:9.1.2-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- valkey-server --appendonly yes --save 30 1 --loglevel warning --requirepass "$VALKEY_PASSWORD"
|
||||
env:
|
||||
- name: VALKEY_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: netbox-secrets
|
||||
key: VALKEY_PASSWORD
|
||||
ports:
|
||||
- name: valkey
|
||||
containerPort: 6379
|
||||
volumeMounts:
|
||||
- name: valkey-data
|
||||
mountPath: /data
|
||||
startupProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", 'valkey-cli --pass "$VALKEY_PASSWORD" ping | grep -q PONG']
|
||||
failureThreshold: 20
|
||||
periodSeconds: 5
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", 'valkey-cli --pass "$VALKEY_PASSWORD" ping | grep -q PONG']
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", 'valkey-cli --pass "$VALKEY_PASSWORD" ping | grep -q PONG']
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: "25m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "250m"
|
||||
memory: "256Mi"
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: valkey-data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
# Shared PostgreSQL
|
||||
|
||||
This directory contains the shared PostgreSQL 17 deployment for Authentik,
|
||||
Gitea, Netronome, and Statuspage. It creates one database and one login role
|
||||
Gitea, NetBox, Netronome, and Statuspage. It creates one database and one login role
|
||||
per service. Per-service standalone databases were removed after the
|
||||
migration (Sep 2026); Penpot stays on its own compose PostgreSQL (archived,
|
||||
not part of the shared instance).
|
||||
@@ -12,6 +12,7 @@ not part of the shared instance).
|
||||
| ---------- | ------------------- | -------------------------------------- |
|
||||
| Authentik | 2025.10.x | Supported (Authentik requires 14+) |
|
||||
| Gitea | 1.27.3 | Supported (Gitea requires 12+) |
|
||||
| NetBox | 4.7.x | Supported (NetBox 4.x requires 13+) |
|
||||
| Netronome | 0.14.0 | Supported (upstream's example uses 17) |
|
||||
| Statuspage | custom | Supported |
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ set -euo pipefail
|
||||
|
||||
: "${AUTHENTIK_DB_PASSWORD:?AUTHENTIK_DB_PASSWORD is required}"
|
||||
: "${GITEA_DB_PASSWORD:?GITEA_DB_PASSWORD is required}"
|
||||
: "${NETBOX_DB_PASSWORD:?NETBOX_DB_PASSWORD is required}"
|
||||
: "${NETRONOME_DB_PASSWORD:?NETRONOME_DB_PASSWORD is required}"
|
||||
: "${PENPOT_DB_PASSWORD:?PENPOT_DB_PASSWORD is required}"
|
||||
: "${STATUSPAGE_DB_PASSWORD:?STATUSPAGE_DB_PASSWORD is required}"
|
||||
@@ -23,6 +24,7 @@ SQL
|
||||
|
||||
create_role_and_database authentik authentik "$AUTHENTIK_DB_PASSWORD"
|
||||
create_role_and_database gitea gitea "$GITEA_DB_PASSWORD"
|
||||
create_role_and_database netbox netbox "$NETBOX_DB_PASSWORD"
|
||||
create_role_and_database netronome netronome "$NETRONOME_DB_PASSWORD"
|
||||
create_role_and_database penpot penpot "$PENPOT_DB_PASSWORD"
|
||||
create_role_and_database statuspage statuspage "$STATUSPAGE_DB_PASSWORD"
|
||||
@@ -17,6 +17,9 @@ spec:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: gitea
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: netbox
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: netronome
|
||||
|
||||
@@ -113,6 +113,7 @@ data:
|
||||
|
||||
: "${AUTHENTIK_DB_PASSWORD:?AUTHENTIK_DB_PASSWORD is required}"
|
||||
: "${GITEA_DB_PASSWORD:?GITEA_DB_PASSWORD is required}"
|
||||
: "${NETBOX_DB_PASSWORD:?NETBOX_DB_PASSWORD is required}"
|
||||
: "${NETRONOME_DB_PASSWORD:?NETRONOME_DB_PASSWORD is required}"
|
||||
: "${PENPOT_DB_PASSWORD:?PENPOT_DB_PASSWORD is required}"
|
||||
: "${STATUSPAGE_DB_PASSWORD:?STATUSPAGE_DB_PASSWORD is required}"
|
||||
@@ -133,6 +134,7 @@ data:
|
||||
|
||||
create_role_and_database authentik authentik "$AUTHENTIK_DB_PASSWORD"
|
||||
create_role_and_database gitea gitea "$GITEA_DB_PASSWORD"
|
||||
create_role_and_database netbox netbox "$NETBOX_DB_PASSWORD"
|
||||
create_role_and_database netronome netronome "$NETRONOME_DB_PASSWORD"
|
||||
create_role_and_database penpot penpot "$PENPOT_DB_PASSWORD"
|
||||
create_role_and_database statuspage statuspage "$STATUSPAGE_DB_PASSWORD"
|
||||
@@ -8,6 +8,7 @@ stringData:
|
||||
POSTGRES_ADMIN_PASSWORD: ""
|
||||
AUTHENTIK_DB_PASSWORD: ""
|
||||
GITEA_DB_PASSWORD: ""
|
||||
NETBOX_DB_PASSWORD: ""
|
||||
NETRONOME_DB_PASSWORD: ""
|
||||
PENPOT_DB_PASSWORD: ""
|
||||
STATUSPAGE_DB_PASSWORD: ""
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
rackpeek:
|
||||
image: docker.io/aptacode/rackpeek:v2.1.0
|
||||
container_name: rackpeek
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
environment:
|
||||
TZ: "Europe/Bratislava"
|
||||
volumes:
|
||||
- rackpeek-config:/app/config
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.rackpeek.loadbalancer.server.port=8080"
|
||||
|
||||
# Local Router
|
||||
- "traefik.http.routers.rackpeek-local.rule=Host(`rackpeek.workstation.internal`) || Host(`rack.workstation.internal`) || Host(`rackpeek.gigaforust.internal`) || Host(`rack.gigaforust.internal`)"
|
||||
- "traefik.http.routers.rackpeek-local.entrypoints=websecure"
|
||||
- "traefik.http.routers.rackpeek-local.tls=true"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
|
||||
start_period: 15s
|
||||
timeout: 5s
|
||||
interval: 30s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
rackpeek-config:
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
Whitespace-only changes.
@@ -0,0 +1,15 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: internal-wildcard-tls
|
||||
namespace: rackpeek
|
||||
spec:
|
||||
secretName: internal-wildcard-tls
|
||||
dnsNames:
|
||||
- "*.workstation.internal"
|
||||
- "*.gigaforust.internal"
|
||||
- workstation.internal
|
||||
- gigaforust.internal
|
||||
issuerRef:
|
||||
name: internal-ca
|
||||
kind: ClusterIssuer
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: rackpeek-local
|
||||
namespace: rackpeek
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`rackpeek.workstation.internal`) || Host(`rack.workstation.internal`) || Host(`rackpeek.gigaforust.internal`) || Host(`rack.gigaforust.internal`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: rackpeek-service
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: internal-wildcard-tls
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: rackpeek
|
||||
@@ -0,0 +1,89 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: rackpeek-service
|
||||
namespace: rackpeek
|
||||
spec:
|
||||
selector:
|
||||
app: rackpeek
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rackpeek-deployment
|
||||
namespace: rackpeek
|
||||
labels:
|
||||
app: rackpeek
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: rackpeek
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rackpeek
|
||||
spec:
|
||||
securityContext:
|
||||
# Image runs as uid/gid 1654
|
||||
fsGroup: 1654
|
||||
containers:
|
||||
- name: rackpeek
|
||||
image: docker.io/aptacode/rackpeek:v2.1.0
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
env:
|
||||
- name: RPK_YAML_DIR
|
||||
value: "/app/config"
|
||||
- name: TZ
|
||||
value: "Europe/Bratislava"
|
||||
volumeMounts:
|
||||
- name: rackpeek-config
|
||||
mountPath: /app/config
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
failureThreshold: 30
|
||||
periodSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: rackpeek-config
|
||||
persistentVolumeClaim:
|
||||
claimName: rackpeek-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: rackpeek-pvc
|
||||
namespace: rackpeek
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: local-path-retain
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
Reference in new issue
Block a user