Third-Party Components
SpatiumDDI is an assembly job as much as it is a codebase. It does not reimplement DNS or DHCP — it runs BIND9, PowerDNS, Technitium and Kea, and it runs them on a Kubernetes distribution it did not write, on an operating system it did not write. This page is the catalogue of everything in that stack: what it is, which artifact it ships in, what license it carries, and why it is there.
Two files cover this ground, and they are not the same thing.
| File | Purpose | Authoritative for |
|---|---|---|
NOTICE |
Legal attribution notice, shipped in the repo root and in every image | License obligations |
| This page | Operator-facing catalogue — versions, artifact placement, rationale | Understanding what runs where |
If the two ever disagree about a license, NOTICE wins and the discrepancy is
a bug worth filing.
Neither file enumerates every transitive dependency. They cover components that
are bundled, shipped, or directly depended on — the things an operator can
point at in a running system, and the things a license auditor asks about. The
full transitive set for the Python and npm layers lives in
backend/pyproject.toml + frontend/package-lock.json; for the OS layer, ask
the package manager on a running appliance (see
Verifying a running system).
Artifacts
The “Where” column throughout this page refers to these:
| Artifact | What it is |
|---|---|
| API image | ghcr.io/spatiumddi/spatiumddi-api — FastAPI control plane, Celery worker, Celery beat, and the migrate job. One image, four roles |
| Frontend image | ghcr.io/spatiumddi/spatiumddi-frontend — the built React SPA behind nginx |
| DNS agent images | dns-bind9, dns-powerdns, dns-technitium, dns-dnsdist — each pairs a DNS engine with the Python sync agent |
| DHCP agent image | dhcp-kea — Kea DHCPv4 + DHCPv6 + radvd + the Python sync agent |
| Looking Glass image | looking-glass — GoBGP collector + agent |
| Supervisor image | spatium-supervisor — host-side controller on the OS appliance |
| Appliance OS | The bootable Debian image, including its baked k3s |
| Helm charts | charts/spatiumddi (generic Kubernetes) and charts/spatiumddi-appliance (on-appliance k3s) |
Versions below are the pins on main at the time of writing. They move; the
Pinned in column names the file that is actually authoritative, and that is
the one to check rather than trusting this table’s numbers.
DNS, DHCP and routing engines
The reason the project exists. Every one of these is a separate upstream daemon running as its own process — SpatiumDDI generates their configuration and talks to their control channels, it does not link against them.
| Component | Version | License | Where | Pinned in |
|---|---|---|---|---|
| ISC BIND 9 | >= 9.20.26 (Alpine) |
MPL 2.0 | DNS agent image | agent/dns/images/bind9/Dockerfile |
| PowerDNS Authoritative Server | Alpine pdns |
GPL v2 | DNS agent image | agent/dns/images/powerdns/Dockerfile |
| Technitium DNS Server | 15.4.0 (digest-pinned) | GPL v3 | DNS agent image | agent/dns/images/technitium/Dockerfile |
| PowerDNS dnsdist | Alpine dnsdist |
GPL v2 | DNS agent image | agent/dns/images/dnsdist/Dockerfile |
| ISC Kea DHCP | Alpine kea, v4 + v6 |
MPL 2.0 | DHCP agent image | agent/dhcp/images/kea/Dockerfile |
| radvd | Alpine radvd |
BSD-style | DHCP agent image | agent/dhcp/images/kea/Dockerfile |
| GoBGP | 4.7.0 (built from source) | Apache 2.0 | Looking Glass image | agent/looking-glass/images/gobgp/Dockerfile |
BIND dig / nsupdate |
bind-tools |
MPL 2.0 | DNS + supervisor + API images | several Dockerfiles |
Notes worth carrying:
- Technitium runs on .NET. Its image is Ubuntu-based (upstream’s own base), not Alpine like the other agents, and it pulls in the ASP.NET Core runtime. It is the one DNS engine whose base image SpatiumDDI does not choose.
- dnsdist is not a general DNS server here. It exists to give PowerDNS inbound DoT/DoH, which pdns-auth does not speak natively. See DNS.
- Kea 3.0 imposes path restrictions on hook libraries and socket paths as a result of CVE-2025-32801/2/3. The Dockerfile carries a prominent warning about this; the paths in it are load-bearing, not stylistic.
Kubernetes and orchestration
| Component | Version | License | Where | Pinned in |
|---|---|---|---|---|
| k3s | v1.35.6+k3s1 | Apache 2.0 | Appliance OS | appliance/scripts/fetch-k3s.sh |
| containerd + runc | embedded in k3s | Apache 2.0 | Appliance OS | (k3s bundle) |
| CoreDNS | k3s airgap bundle | Apache 2.0 | Appliance OS | (k3s bundle) |
| Flannel | embedded in k3s, host-gw backend |
Apache 2.0 | Appliance OS | appliance/mkosi.extra/etc/rancher/k3s/config.yaml |
| local-path-provisioner | k3s airgap bundle | Apache 2.0 | Appliance OS | (k3s bundle) |
| etcd | embedded in k3s | Apache 2.0 | Appliance OS | (k3s bundle) |
| Helm | k3s helm-controller | Apache 2.0 | Appliance OS | (k3s bundle) |
| Traefik | in k3s bundle, disabled | MIT | Appliance OS | config.yaml disable: list |
| metrics-server | in k3s bundle, disabled | Apache 2.0 | Appliance OS | config.yaml disable: list |
| MetalLB | chart + images 0.15.3 | Apache 2.0 | Helm chart (opt-in) | charts/spatiumddi-metallb/Chart.yaml |
| FRRouting | via MetalLB frr-k8s | GPL v2 | Helm chart (opt-in) | charts/spatiumddi-metallb values |
| CloudNativePG | chart 0.28.2 | Apache 2.0 | Helm chart (opt-in) | charts/spatiumddi-appliance/Chart.yaml |
| Patroni | k8s/ha/ overlay |
MIT | Bare-metal HA overlay | k8s/ha/ |
| HAProxy | 2.9-alpine | GPL v2 (+ LGPL libs) | Patroni HA overlay | k8s/ha/ |
Three of these deserve a sentence, because their state is not what you would assume from their presence:
- Traefik and metrics-server ship but are disabled. They arrive inside the
k3s airgap image tarball, which is a single artifact — you cannot fetch a k3s
release without them. The appliance disables both in
config.yaml: operator TLS goes through the existing nginx reverse proxy, and metrics-server costs ~80 MiB RSS for something not yet surfaced. - MetalLB is pinned to 0.15.3 deliberately, chart and images together. 0.16.0 regressed the speaker into an apiserver-flooding reconcile loop (metallb#3063), and pinning only the images while keeping the newer chart crashloops on a health probe the older binary does not serve.
- FRRouting is GPL v2 and only enters the picture when an operator opts into
BGP-mode MetalLB. Both
bgp.enabledandfrrk8s.enableddefault to false.
Data services
| Component | Version | License | Where | Pinned in |
|---|---|---|---|---|
| PostgreSQL | 16-alpine | PostgreSQL License | Compose + Helm chart | docker-compose.yml |
| Redis | 8.8-alpine | RSALv2 / SSPLv1 / AGPLv3 (Redis 8+) | Compose + Helm chart | docker-compose.yml |
| Prometheus node-exporter | v1.11.1, default off | Apache 2.0 | Appliance chart | charts/spatiumddi-appliance/values.yaml |
| kube-state-metrics | v2.18.0, default off | Apache 2.0 | Appliance chart | charts/spatiumddi-appliance/values.yaml |
| prometheus-client | see manifest | Apache 2.0 AND BSD 2-Clause | API image | backend/pyproject.toml |
On Redis’s license: Redis relicensed away from BSD at 7.4 (RSALv2 + SSPLv1)
and added an AGPLv3 option at 8.0. SpatiumDDI ships the stock upstream
redis:8.8-alpine image unmodified and talks to it as a network service over
its own protocol — no Redis code is linked into or vendored by SpatiumDDI.
Operators who would rather not run that license family can point the deployment
at any Redis-protocol-compatible server via the connection URL. The control
plane uses only GET / SET / DEL / INCR / EXPIRE / SCAN and pub/sub —
no modules, no Lua, no streams — and Celery’s Redis broker is the other
consumer. This is an untested configuration rather than a supported one; if you
run SpatiumDDI on a Redis alternative,
tell us how it went.
The Helm chart used to bundle Bitnami’s PostgreSQL and Redis subcharts. Bitnami pruned its Docker Hub namespace in late 2025 in favour of paid images, so the pinned tags stopped resolving; the chart now ships plain StatefulSets using the official upstream images instead.
Control-plane backend (Python)
Runs in the API image. Authoritative list, with versions:
backend/pyproject.toml.
| Component | License | What it does here |
|---|---|---|
| Python 3.12 | PSF | Runtime |
| FastAPI / Starlette | MIT / BSD 3-Clause | HTTP framework |
| Uvicorn | BSD 3-Clause | ASGI server |
| SQLAlchemy / Alembic | MIT | ORM + migrations |
| asyncpg | Apache 2.0 | Async PostgreSQL driver |
| Pydantic / pydantic-settings | MIT | Schema validation, settings |
| Celery | BSD 3-Clause | Task queue |
| redis-py + hiredis | MIT | Redis client |
| structlog | MIT OR Apache 2.0 | Structured JSON logging |
| cryptography | Apache 2.0 OR BSD 3-Clause | Fernet secrets at rest, cert handling |
| pyOpenSSL | Apache 2.0 | TLS chain capture (stdlib ssl cannot on 3.12) |
| python-jose / joserfc | MIT / BSD 3-Clause | JWT issue + verify |
| bcrypt | Apache 2.0 | Local password hashing |
| pyotp | MIT | TOTP second factor |
| ldap3 | LGPL v3 | LDAP authentication |
| python3-saml | MIT | SAML SP flow |
| pyrad | BSD 3-Clause | RADIUS authentication |
| tacacs_plus | BSD | TACACS+ authentication |
| httpx | BSD 3-Clause | HTTP client (all outbound integrations) |
| dnspython | ISC | DDNS, AXFR, resolution |
| Jinja2 | BSD 3-Clause | Config rendering |
| pywinrm | MIT | Windows DNS/DHCP over WinRM |
| pysnmp | BSD 2-Clause | SNMP polling |
| paramiko | LGPL 2.1 | SSH transport |
| smbprotocol | MIT | SMB backup targets |
| openpyxl | MIT | XLSX import/export |
| reportlab | BSD | PDF reports (audit, compliance, IPAM) |
| croniter | MIT | Schedule parsing |
| icalendar / caldav / python-dateutil | BSD 2-Clause / Apache 2.0 (dual with GPL v3) / dual | Wake-on-LAN calendar gate |
| idna | BSD 3-Clause | IDNA2008 name normalization |
| boto3 | Apache 2.0 | AWS mirror + Route 53 DNS + S3 backups |
| azure-identity / azure-mgmt-* | MIT | Azure mirror + Azure DNS + Blob backups |
| google-cloud-* / google-auth | Apache 2.0 | GCP mirror + Cloud DNS + GCS backups |
| docker | Apache 2.0 | Cert-reload signal on non-k3s appliances |
| PyYAML | MIT | HelmChartConfig rewriting |
| openai / anthropic | Apache 2.0 / MIT | Operator Copilot model clients |
Cloudflare has no SDK entry on purpose. The Cloudflare DNS driver speaks
plain REST over httpx rather than pulling in the official SDK, which ships a
pydantic-v1 compatibility shim.
On the two copyleft Python libraries: ldap3 (LGPL v3) and paramiko
(LGPL 2.1) are imported as libraries into an Apache-2.0 application. LGPL
permits this; what it requires is that a recipient can replace the library. They
are installed as ordinary, separately-replaceable site-packages in the image —
nothing is statically linked or vendored.
Agent runtimes (Python)
The DNS, DHCP, Looking Glass and supervisor agents each carry a much smaller
dependency set than the control plane. Common to all: httpx, structlog,
pydantic.
| Agent | Additional | License | Why |
|---|---|---|---|
| DNS | dnspython, cryptography | ISC, Apache 2.0 OR BSD 3-Clause | DDNS + TSIG |
| DHCP | Scapy | GPL v2 | Passive DHCP fingerprinting — opt-in at runtime, installed unconditionally so the toggle needs no second image |
| Supervisor | cryptography, PyYAML | Apache 2.0 OR BSD 3-Clause, MIT | Ed25519 identity, chart values |
| Looking Glass | — | — | Talks to GoBGP over its API |
Frontend
Runs in the frontend image. Authoritative list:
frontend/package.json.
| Component | License | What it does here |
|---|---|---|
| React + react-dom 18 | MIT | UI framework |
| TypeScript | Apache 2.0 | Language |
| Vite | MIT | Build tool + dev server |
| Tailwind CSS + tailwindcss-animate | MIT | Styling |
| shadcn/ui | MIT | Component patterns (vendored source, not a dependency) |
| Radix UI context-menu | MIT | Accessible primitive |
| TanStack Query | MIT | Server-state cache |
| React Router | MIT | Routing |
| axios | MIT | HTTP client |
| Recharts | MIT | Charts |
| lucide-react | ISC | Icons |
| react-markdown + remark-gfm | MIT | Copilot message rendering |
| dnd-kit | MIT | Drag-and-drop |
| clsx, tailwind-merge | MIT | Class composition |
| nginx 1.31-alpine | BSD 2-Clause | Static serving + API reverse proxy |
Appliance operating system
The bootable image is Debian 13 (trixie), built with
mkosi. The full package list is
appliance/mkosi.conf — this is the operator-meaningful subset.
| Component | License | Why it is on the image |
|---|---|---|
| Linux kernel | GPL v2 | linux-image-amd64, not the cloud variant — the cloud kernel strips KMS, leaving an 80x25 console instead of the dense one the dashboard needs |
| Debian base | DFSG (mixed) | Base distribution |
| systemd + udev + systemd-resolved | LGPL 2.1+ | Init, device management, host resolver |
| GRUB 2 | GPL v3 | Hybrid BIOS + UEFI boot |
| NetworkManager | GPL v2+ | Interface configuration |
| nftables | GPL v2 | Default-deny inbound firewall, per-role drop-ins |
| chrony | GPL v2 | NTP |
| Net-SNMP | BSD-style | snmpd — needs unfiltered /proc and /sys, which is why it runs on the host and not in a container |
| lldpd | ISC | Neighbour discovery |
| rsyslog + rsyslog-gnutls | GPL v3 / LGPL v3 | Syslog forwarding, TLS transport |
| OpenSSH | BSD-style | Operator access |
| cloud-init | GPL v3 / Apache 2.0 | First-boot provisioning |
| unattended-upgrades | GPL v2 | Security patches (no kernel upgrades) |
| newt / whiptail | LGPL v2 | Install wizard dialogs |
| gdisk, dosfstools, e2fsprogs, parted | GPL v2 / GPL v3 | Disk partitioning + filesystems |
| rsync | GPL v3 | Rootfs mirror to target disk |
| qemu-guest-agent / open-vm-tools | GPL v2 / GPL v2 + BSD | Hypervisor integration; both no-op off their platform |
| zstd | BSD 3-Clause / GPL v2 | Image + airgap tarball compression |
| jq, curl, less, htop, tcpdump, dnsutils | MIT / curl / GPL / BSD / MPL 2.0 | Operator diagnostics |
| Rich + psutil (python3-rich, python3-psutil) | MIT / BSD 3-Clause | The console dashboard on tty1 |
There is no Docker on the appliance. It was removed in #183 Phase 7 — the
image is k3s-only, and k3s’s static binary carries its own containerd plus
kubectl / ctr / crictl as argv symlinks.
Operator tooling inside the API image
The Tools page (/tools) and PCAP capture shell out to real binaries, so they
ship in the API image rather than being reimplemented. All are invoked through a
sandboxed argv builder, never a shell string.
| Component | License | Used by |
|---|---|---|
| nmap | NPSL (Nmap Public Source License) | Port/service scan tools |
| tcpdump + libpcap | BSD 3-Clause | PCAP capture (#59), granted cap_net_raw |
| mtr (mtr-tiny) | GPL v2 | Path analysis |
| iputils-ping, traceroute | BSD / GPL v2 | Reachability |
| whois | GPL v2 | Registration lookup |
BIND dig (dnsutils) |
MPL 2.0 | DNS query + propagation check |
| PostgreSQL client 16 | PostgreSQL License | pg_dump / pg_restore for backup + restore |
| xmlsec1 (libxmlsec1-openssl) | MIT | SAML assertion signature verification |
nmap’s license is not OSI-approved. The NPSL is a GPL v2 derivative with added restrictions on redistribution inside commercial products. SpatiumDDI is Apache 2.0 and ships nmap as an unmodified, separately-installed Debian package invoked as a subprocess — no linking, no derivative work. Anyone repackaging SpatiumDDI commercially should read the NPSL themselves rather than assume the Apache license covers it.
Container bases and init
| Component | Version | License | Where |
|---|---|---|---|
| Alpine Linux | 3.23 | MIT (+ GPL v2 kernel) | All agent images, supervisor |
| Debian slim | trixie / bookworm | DFSG (mixed) | Appliance builder, Technitium agent build stage |
| python:3.12-slim | 3.12 | PSF + Debian | API image |
| node | 22-alpine | MIT | Frontend build stage |
| nginx | 1.31.3-alpine | BSD 2-Clause | Frontend runtime |
| ASP.NET Core runtime | 10.0 | MIT | Technitium agent image |
| tini | Alpine/Debian pkg | MIT | PID 1 in every agent image |
| su-exec / gosu | Alpine / Debian pkg | MIT / Apache 2.0 | Privilege drop at entrypoint |
Alpine is pinned at 3.23, not 3.24, and that pin is load-bearing: 3.24 ships
Python 3.13 while the agent Dockerfiles install into 3.12 paths, which produces a
ModuleNotFoundError crashloop rather than a build failure. Dependabot is
configured to hold the pin.
Build and test toolchain
Not shipped to operators — listed because a contributor will meet all of it, and because CI gates depend on specific behaviour from several.
| Component | License | Role |
|---|---|---|
| mkosi | LGPL 2.1 | Builds the appliance disk image |
| Go | BSD 3-Clause | Builds GoBGP from source in the Looking Glass image |
| Trivy | Apache 2.0 | Image CVE gate (HIGH/CRITICAL, --ignore-unfixed) |
| pytest + pytest-asyncio / cov / xdist / split | MIT | Backend test suite |
| ruff / black / mypy | MIT | Backend lint + typecheck |
| ESLint / Prettier / typescript-eslint | MIT | Frontend lint |
| Playwright | Apache 2.0 | README/docs screenshot generation |
| Jekyll + kramdown | MIT | This documentation site |
| Helm | Apache 2.0 | Chart packaging |
License obligations
SpatiumDDI itself is Apache 2.0. Everything above keeps its own license. The combination is distributed as separate, independently-installed programs — container images built from upstream distribution packages, and a disk image built by a distribution’s own package manager. Nothing copyleft is statically linked into, or vendored inside, the Apache-2.0 codebase.
The practical consequences:
- GPL components ship unmodified. BIND9, PowerDNS, dnsdist, Technitium, Scapy, FRRouting, HAProxy and the Debian host userland are all upstream binaries installed from their distributions’ repositories. Source for each is available from the distribution that packaged it, on the terms in that package’s license.
- The two LGPL Python libraries (
ldap3,paramiko) are imported, not linked or vendored, and are replaceable in place inside the image. - nmap’s NPSL is the one license here that is not OSI-approved and carries redistribution conditions Apache 2.0 does not. See the note above.
- Technitium is GPL v3 and, unlike the other engines, runs from upstream’s own container image with a digest pin. SpatiumDDI adds the sync agent as a separate process in that image; it does not patch the server.
If you are performing a license review and need something this page does not answer, open an issue — that is a documentation bug, not a support question.
Verifying a running system
This page can drift. A running system cannot.
Python packages in the API image, with versions and licenses:
docker compose exec api python3 -m pip list
docker compose exec api python3 -m pip show ldap3 paramiko
OS packages on the appliance:
dpkg-query -W -f='${Package}\t${Version}\t${Homepage}\n' | sort
Packages inside an Alpine-based agent image:
docker compose exec dns-bind9 apk list --installed
What k3s actually bundles in the release you are running — the manifest is baked onto the appliance at build time, alongside k3s’s own LICENSE:
cat /usr/share/doc/k3s/k3s-images.txt
cat /usr/share/doc/k3s/LICENSE
Images running on the appliance’s k3s cluster right now:
kubectl get pods -A -o jsonpath='{range .items[*].spec.containers[*]}{.image}{"\n"}{end}' | sort -u
See also
NOTICE— the shipped attribution notice- Architecture — how these components fit together
- DNS Drivers / DHCP Drivers — how SpatiumDDI drives the engines
- OS Appliance — how the image is built
- Kubernetes — the umbrella chart