Skip to content

Quick reference

Everything on this page is documented for SpatiumDDI 1.0.0-beta.3, and each table names its source. It is laid out to print: use your browser’s Print command to keep a copy next to the keyboard. For the reasoning behind these values, see Tips and tricks and the Troubleshooting playbook.

What Value
Start or resume the lab ./lab/up.sh
Start with the upstream demo data ./lab/up.sh --seed
Keep admin / admin for the first-login exercise ./lab/up.sh --first-login
Stop the lab, keep its data ./lab/down.sh
Delete everything and start fresh ./lab/destroy.sh
Put admin back to admin (forced change) ./lab/reset-admin.sh
Check lab n ./lab/check.sh <n>
Web UI http://localhost:8077
REST API http://localhost:8077/api/v1/… (the API container is also published on 127.0.0.1:8000, for the lab host only)
Interactive API docs http://localhost:8077/api/docs (Swagger) and /api/redoc; sign in to the console first
Sign in admin with the LAB_ADMIN_PASSWORD from lab/.env
DNS (BIND9 agent) 127.0.0.1, port 1053, UDP and TCP
DHCP (Kea agent) host port 6767/udp (bridged: it can’t serve real LAN clients)
SpatiumDDI version 1.0.0-beta.3, pinned by SPATIUMDDI_VERSION in lab/.env
Feature modules 16 of 57 on, as on any fresh install (Features & Integrations)
Where to run docker compose inside lab/ (Compose project spatium-academy)

Source: the Academy lab scripts (lab/up.sh, lab/.env.example, lab/docker-compose.yml); verified in the Academy lab, 1.0.0-beta.3.

Object Value
DNS and DHCP server groups default (agents dns-bind9 and dhcp-kea)
Forward zone corp.example.edu
IP space, block Campus, 10.20.0.0/16 “Ridgeline campus” (groups and zone pinned here)
Staff subnet 10.20.21.0/24 “Staff VLAN 21” (VLAN convention 10.20.<VLAN>.0/24)
Reverse zone 21.20.10.in-addr.arpa, created automatically when you save Staff VLAN 21 (one zone per /24, inherited DNS included)
DHCP pools dynamic 10.20.21.100–199 (the scope’s initial pool, named default, in place of the suggested .10–.254); excluded 10.20.21.1–9 (“Network infrastructure”)
First host print-01 = 10.20.21.10 (A in corp.example.edu, PTR in 21.20.10.in-addr.arpa)
Reservation cam-01 = 10.20.21.20, MAC 52:54:00:12:34:56, status static_dhcp
Bulk hosts lab-pc-01 … lab-pc-10 = 10.20.21.30–39
Scratch subnet 10.20.99.0/24 “Scratch” (its reverse zone 99.20.10.in-addr.arpa is created the same way)
RBAC group Helpdesk with role Viewer; user jsmith
API token ridgeline-automation

Source: Getting Started, the worked example and the Academy’s running example.

Port Protocol Used by Notes
8077 TCP (HTTP) Web UI, API and API docs (frontend nginx) HTTP_PORT; the container listens on 80
443 TCP (HTTPS) Web UI with TLS When you configure TLS
8000 TCP (HTTP) API (uvicorn) API_PORT; published on 127.0.0.1 only by default (API_BIND)
5432 TCP PostgreSQL Internal only; never expose
6379 TCP Redis Internal only; never expose
53 UDP + TCP DNS daemons Compose publishes 1053 (BIND9), 5453 (PowerDNS), 6053 (Technitium)
67 UDP DHCPv4 (Kea) Compose publishes 6767 (HA peer: 6768)
547 UDP DHCPv6 (Kea) The appliance DHCP role opens 67, 68 and 547
8000 in the Kea container TCP Kea HA peer traffic Compose publishes 8001 (HA peer: 8002)
853 TCP DNS over TLS Compose overlay publishes 1853 for BIND9
443 (8443 on an appliance) TCP DNS over HTTPS Compose overlay publishes 8443 for BIND9
853 UDP DNS over QUIC Technitium only
8081 / 5380 TCP PowerDNS / Technitium REST API Loopback only, inside the agent container
5985 / 5986 TCP WinRM (HTTP / HTTPS) Windows DNS Path B and Windows DHCP; prefer 5986
53 on the Windows DC UDP + TCP RFC 2136 updates and AXFR Windows DNS Path A
6443, 2379–2380, 10250 TCP k3s API, etcd, kubelet Appliance cluster peers only
7946 TCP + UDP MetalLB memberlist Appliance control-plane peers only
19443 TCP frr-k8s webhook Appliance control-plane peers only
179 TCP BGP Looking Glass collector Uses host networking; the module ships off

Sources: Docker Compose deployment §1 and §10 · README, encrypted DNS · DNS drivers · Windows Server setup · Deployment topologies, fleet firewall (#1827) · Troubleshooting (#1139)

Terminal window
# Start the stack with the built-in DNS and DHCP containers
docker compose --profile dns-bind9 --profile dhcp up -d
# or set COMPOSE_PROFILES=dns-bind9,dhcp in .env and run: docker compose up -d
# other DNS profiles: dns-powerdns, dns-technitium (dns = alias of dns-bind9)
# Status and logs
docker compose ps
docker compose logs -f api worker # control plane (NDJSON on stdout)
docker compose logs -f dns-bind9 # BIND9 agent and daemon
docker compose logs -f dhcp-kea # Kea agent and daemon
docker compose logs --tail=20 beat | grep 'Sending due task'
# Restart one agent
docker compose restart dns-bind9
# Upgrade: take a backup first, set SPATIUMDDI_VERSION=<tag> in .env, then
git pull # outside the lab: refresh docker-compose.yml and .env.example
docker compose pull # with --profile flags instead of COMPOSE_PROFILES,
docker compose run --rm migrate # pass them to pull and up -d; migrate is safe to
docker compose up -d # repeat and resumes after a failure ("deadlock detected")
docker compose images # confirm every container runs the new tag
# Before upgrading from 2026.09.04-1: copy local-volume backups out of the containers
docker compose cp api:/var/lib/spatiumddi/backups ./backups-api
docker compose cp worker:/var/lib/spatiumddi/backups ./backups-worker
# See what Kea is really running
docker compose exec dhcp-kea \
cat /var/lib/spatium-dhcp-agent/rendered/kea-dhcp4.json | grep -A4 subnet4
# Publish DoT and DoH for BIND9 (overlay file)
docker compose -f docker-compose.yml -f docker-compose.dns-encrypted.yml \
--profile dns-bind9 up -d

Coming from 2026.09.04-1, upgrade to 2026.10.02-1 first, then to 1.0.0-beta.3; the upgrade checklist has the steps.

Sources: README, upgrading and running the built-in containers · Docker Compose deployment §8 and §9 · Troubleshooting · Observability §3

Terminal window
# Forward record (A) against the lab BIND9 agent
dig @127.0.0.1 -p 1053 print-01.corp.example.edu A +short
# Reverse record (PTR)
dig @127.0.0.1 -p 1053 -x 10.20.21.10 +short
# Is the zone loaded? Ask for its SOA and NS
dig @127.0.0.1 -p 1053 corp.example.edu SOA +short
dig @127.0.0.1 -p 1053 corp.example.edu NS +short
# Is the reverse zone loaded?
dig @127.0.0.1 -p 1053 21.20.10.in-addr.arpa SOA +short
# The gateway's PTR (published when the subnet is created)
dig @127.0.0.1 -p 1053 -x 10.20.21.1 +short
# Round robin: every A record for one name
dig @127.0.0.1 -p 1053 <hostname>.corp.example.edu A +short
# DoT and DoH, once the encrypted overlay is running
dig +tls +tls-hostname=<cert-name> @127.0.0.1 -p 1853 <name> A +short
dig +https=/dns-query +tls-hostname=<cert-name> @127.0.0.1 -p 8443 <name> A +short

On a production server, drop -p 1053 and query port 53 on the server’s address. A blank Primary NS and Admin Email produce an SOA naming ns1.<zone> and admin.<zone>; values you fill in are served as entered.

Sources: README, running the built-in containers and encrypted DNS · Getting Started §10 · GitHub issue #1150 · GitHub issue #1153

Terminal window
# DHCP host (appliance): are UDP 67, 68 and 547 open in the firewall?
sudo nft list chain inet filter input | grep -E 'dport (67|68|547)'
# Does kea-dhcp6 listen on the global address a DHCPv6 relay sends to?
ss -ulpn6 | grep 547
# Appliance: compare nmtui edits with the saved state, or save them back
spatium-network-adopt --check
spatium-network-adopt --adopt
# Appliance control plane: back up the Secret that holds SECRET_KEY (keep it off the box)
kubectl -n spatium get secret spatium-control-app-keys -o yaml > app-keys.yaml
# Why does an older release's api wait forever? (database migrated by a newer release)
kubectl -n spatium logs deploy/spatium-control-spatiumddi-api -c wait-for-migrate
# CNPG: rebuild one lost standby from the primary (never the primary)
kubectl -n spatium cnpg destroy <cluster> <n>

Sources: Troubleshooting (#1140, #2317) · OS appliance, network and rolling back (#1016, #1448)

What Where
API base /api/v1
Swagger UI / ReDoc /api/docs / /api/redoc, through the web port; sign in to the console first
OpenAPI schema /api/openapi.json with a session or API token (401 otherwise); the openapi.json attached to every release needs none
Running version GET /api/v1/version; anonymous callers get "version": "restricted"
Your effective permissions GET /api/v1/auth/me/permissions
One audit row in full GET /api/v1/audit/{id} (needs read on audit_log)
Approve a pending DNS agent POST /api/v1/dns/groups/{group_id}/servers/{server_id}/approve (superadmin; DHCP: POST /api/v1/dhcp/servers/{id}/approve)
Rotate a DNS group’s TSIG key POST /api/v1/dns/groups/{id}/group-tsig-key/rotate (superadmin)
Cross-space integration report GET /api/v1/ipam/integration-space-audit (superadmin)
MCP endpoint (Operator Copilot) /api/v1/ai/mcp, read-only tools, module ai.copilot
About page /about in the console (ⓘ About): version, license and third-party notices; signed-in users only
Terminal window
SPATIUM=http://localhost:8077
# Sign in: the body carries access_token (15 min); the 7-day refresh token
# arrives as the HttpOnly spatium_refresh cookie, never in the body
curl -s -X POST "$SPATIUM/api/v1/auth/login" \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<your-password>"}'
# Use a session token or an sddi_ API token the same way
TOKEN='<your-token>'
curl -s "$SPATIUM/api/v1/ipam/spaces" -H "Authorization: Bearer $TOKEN"
# Change a forced password (until you do, almost every other call is refused)
curl -s -X POST "$SPATIUM/api/v1/auth/change-password" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"current_password":"<old>","new_password":"<new>"}'
# Mint an API token scoped like the course's (read + ipam:write). Minting asks for
# your password again (SSO accounts: "stepup_totp_code"); the sddi_ value is returned once
curl -s -X POST "$SPATIUM/api/v1/api-tokens" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"ridgeline-automation","scopes":["read","ipam:write"],"expires_in_days":90,"stepup_password":"<your-password>"}'
# Allocate the next free IP in a subnet (skips dynamic pools)
curl -s -X POST "$SPATIUM/api/v1/ipam/subnets/<subnet-id>/next" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"hostname":"<hostname>","status":"allocated"}'
# Page through a subnet's addresses (total in the X-Total-Count header)
curl -s -D - "$SPATIUM/api/v1/ipam/subnets/<subnet-id>/addresses?limit=100&offset=0" \
-H "Authorization: Bearer $TOKEN"
# Fetch the OpenAPI schema from a script
curl -s "$SPATIUM/api/openapi.json" -H "Authorization: Bearer $TOKEN" -o openapi.json
# Scrape Prometheus metrics: the scrape token (PROMETHEUS_METRICS_TOKEN) or any API token
curl -s "$SPATIUM/metrics" -H "Authorization: Bearer $TOKEN" | head

List shapes. Core IPAM, DNS and DHCP lists return a bare JSON array. Newer routers return {"items": […], "total": n, "limit": n, "offset": n}. DNS records and DHCP leases return items, total, page and page_size, and the audit list total and items.

Token scopes. read (GET, HEAD and OPTIONS only), ipam:write, dns:write, dhcp:write and agent. Several scopes combine; no scopes means “whatever the owner may do”. A token never exceeds its owner.

Status Meaning
400 Semantically invalid input (for example a wrong current password)
401 Missing, invalid or expired credential, or a token scope that doesn’t cover the call (“Token scope insufficient for this request”)
403 Authenticated but not allowed (RBAC, superadmin-only, disabled account, forced password change, a wrong step-up answer)
404 Not found, or the feature module is disabled
409 Conflict or collision (warnings come with requires_confirmation: true; re-send with force=true), or a delete refused because rows still reference the target
413 Request body over the API’s cap (MAX_REQUEST_BODY_BYTES, 8 MiB by default; backups, images and importers have larger limits)
422 Validation failure, or a reference to a row that doesn’t exist
429 Rate limit or attempt budget: sign-in throttles, MFA and step-up budgets, appliance registration (some carry Retry-After)
503 Maintenance mode (Retry-After: 120), a transient database error, or a failing readiness check

Every response carries an X-Request-ID to match against the server logs. Errors use {"detail": …}.

Sources: REST API · Auth & permissions, API tokens and step-up (#1355) · IPAM features §5 · System administration §5 · Verified in the Academy lab, 1.0.0-beta.3

Endpoint Answers Login
/health/live 200 while the process is up None
/health/ready Database, Redis, and the schema at head; 503 with details when not ready (an api already ready stays ready on a newer schema that still has every column it uses) None
/health/startup Same checks as ready, for slow-starting Kubernetes containers None
/health/platform Always 200; the per-component rollup (api, postgres, redis, celery-workers, celery-beat) for a signed-in caller or API token, only the overall status for anyone else Optional
/metrics Prometheus API-request and process metrics (on by default) Bearer: the scrape token (PROMETHEUS_METRICS_TOKEN) or an API token

A red celery-beat means no heartbeat tick landed; the fault can be beat or the workers (see the playbook).

Sources: REST API §1 (#1159, #2777, #2935) · Observability §6 and §7

Role What it grants
Superadmin Everything (* on *)
Viewer Read on everything
IPAM Editor Admin on spaces, blocks, subnets, addresses, address sets, VLANs, NAT, custom fields, IPAM templates, customers, sites, providers, services
DNS Editor Creates, edits and deletes zones, records, views, ACLs and blocklist entries, and manages the TLS certificate store; reads DNS pools. Server groups, servers, TSIG keys, DNSSEC and pools stay with superadmins
DHCP Editor Admin on scopes, pools, reservations, client classes, option templates and MAC blocks. DHCP servers and server groups stay with superadmins
Network Editor Admin on network devices, scans, packet capture, block sync, network tools, ASNs, VRFs, circuits and related network objects
Auditor Read on conformity, the audit log, IPAM (spaces, blocks, subnets, addresses), DNS (groups, zones, records), DHCP scopes and TLS certificates
Compliance Editor Admin on conformity; read on the audit log, IPAM, DNS groups, zones and records, and DHCP scopes
Address Set Editor Admin on address sets only
Change Approver Approve and read change requests (also needs the change’s own permission; never their own request)
Requester Submit and read their own provisioning requests; can’t provision anything. Also grant read on the subnets, blocks, zones or scopes a department may request into
Appliance Operator Admin on the OS appliance management surface and the TLS certificate store; Fleet management (upgrade images, member actions, MetalLB, pairing codes) still needs a superadmin

Every built-in role can read alerts. Built-in roles can be cloned but not deleted. Actions are read, write, delete, admin (all three), approve (the second-person decision on a change request, which admin doesn’t include) and *. A custom role needs read on each IPAM and DNS type it should see: ip_space, ip_block, subnet, ip_address, dns_group, dns_zone, dns_record.

Source: Permissions model (#1861, #3058, #3067, #3073)

Status Meaning
available Not assigned and not seen
allocated Assigned manually in IPAM
reserved Held but not assigned (the auto-created gateway row uses it)
dhcp An active DHCP lease, mirrored into IPAM (locked: edit it at the DHCP source)
static_dhcp Has a DHCP reservation; needs a MAC, and SpatiumDDI puts it on the subnet’s scope
discovered Seen on the network but not recorded in IPAM
orphan Deleted through the UI (Mark as Orphan); its DNS records leave the zone until you restore it, or purge it
deprecated Was assigned, now decommissioned; still counts as allocated until you delete the row

Each subnet also gets network and broadcast placeholder rows (not on /31 and /32), which can’t be edited or allocated.

Sources: IPAM features §5, §8, §11 and §14 · DHCP features §3

Record types Where the API accepts them
A, AAAA, CNAME, MX, TXT, NS, PTR, SRV, CAA, TLSA, SSHFP, NAPTR, LOC Any zone
SVCB, HTTPS, DNAME Groups whose servers run BIND9, PowerDNS or Technitium
ALIAS (CNAME at the apex), LUA (scripted answers) PowerDNS groups only

SRV records need a priority, a weight and a port. A CNAME must be alone at its name and never at the apex. Per-IP DNS aliases in IPAM are CNAME or A records.

Sources: DNS features §0 and §5 · IPAM features §16 · Migration, CNAME beside other data

Keys Where Does
Ctrl+K (⌘K on a Mac) Anywhere Opens Search or jump to…
↑ ↓, then Enter In the search palette Moves through results and opens one
? Anywhere, when not typing Shows every shortcut for your version
Esc Palette and dialogs Closes
Shift+Click IP address table checkboxes Selects a range of rows

Press ? in the app for the authoritative list.

Sources: IPAM features §10 · GitHub issue #81, shortcut overlay

The Compose .env settings this course mentions. The agent variables go in the standalone agent compose files’ .env.

Variable Default What it does
SPATIUMDDI_VERSION latest (the lab pins 1.0.0-beta.3) Image tag for every component; pre-releases are only reachable by pinning
COMPOSE_PROFILES unset Which agent containers start, for example dns-bind9,dhcp
HTTP_PORT / API_PORT 8077 / 8000 Host ports of the web UI and the API
API_BIND 127.0.0.1 Host address the API port is published on
SECRET_KEY none Signs sessions and derives the credential key; the api refuses a placeholder or anything under 32 characters. Replace one with python -m app.core.rotate_secret_key (Rotating SECRET_KEY in the Docker docs)
CREDENTIAL_ENCRYPTION_KEY empty (derived from SECRET_KEY) Fernet key for stored credentials; an invalid one stops the api
ALLOW_INSECURE_SECRET_KEY false Boots on a weak key with a warning; local development only
TRUSTED_PROXY_CIDRS private, loopback, CGNAT and ULA ranges Peers whose X-Real-IP and X-Forwarded-Proto the API believes
FRONTEND_TRUSTED_PROXY_CIDRS unset Your own reverse proxy in front of the frontend
LOGIN_FAIL_MAX_PER_USERNAME / LOGIN_FAIL_MAX_GLOBAL 20 / 500 Failed passwords per 15 minutes before sign-in answers 429; 0 turns one off
PROMETHEUS_METRICS_TOKEN empty (API tokens only) Scrape token for /metrics
PROMETHEUS_METRICS_REQUIRE_AUTH true false serves /metrics to anyone (not recommended)
MAX_REQUEST_BODY_BYTES 8 MiB Request body cap; over it the API answers 413
DNS_REQUIRE_AGENT_APPROVAL / DHCP_REQUIRE_AGENT_APPROVAL false New agents wait for approval before they get config
DNS_BIND9_MEM_LIMIT 2g Memory limit of the dns-bind9 container; size it for your blocklists
SPATIUM_DRILL_MAX_DB_BYTES 20 GiB Restore drills refuse a larger database; 0 disables the guard
TLS_CA_PATH (agents) unset CA bundle a remote agent verifies the control plane with
SPATIUM_INSECURE_SKIP_TLS_VERIFY (agents) 0 1 turns verification off: labs only

Sources: Docker Compose deployment §3 and §10 (#1159, #1220, #1221, #1222) · Docker Compose .env.example · REST API §8 · System administration §2.9

Setting Documented value Source
First sign-in admin / admin, password change forced and held to the password policy Docker §2
Access token / refresh token 15 min / 7 days (refresh rotates; HttpOnly spatium_refresh cookie) REST API §3
Password policy 12+ characters, upper, lower and digit; last 5 refused; no symbol rule, no expiry System admin §6
Password length At most 72 bytes (from 2026.08.22-1) Release notes
Account lockout Off (threshold 0); 15 min lock and reset window when on System admin §6
Sign-in throttles 30 attempts / 60 s per source; 20 failed per username and 500 install-wide per 15 min; then 429 Auth, sign-in throttles
Step-up budget 5 wrong answers per 15 min per account, then 429 Auth, TOTP MFA
Idle session timeout 60 min (Session & Security → Session Timeout, enforced by the API; API tokens unaffected); Auto-Logout Warning 0 (off) System admin §6
API token expiry (UI) 90 days Auth §API tokens
Feature modules on a fresh install 16 of 57 on REST API §5
Default zone TTL / zone type 3600 s / primary System admin §6
Recursion on new DNS groups On, answering only loopback, local and private networks DNS §3.2
DNSSEC signing One agent DNS server per group; BIND9 and PowerDNS only DNS §0
DoT / DoH ports 853 / 443 (8443 on an appliance) DNS §20
DNS pool (GSLB) TTL 30 s DNS §17
DDNS Off per subnet; policy client_or_generated DNS §7
Default DHCP lease time 86400 s (24 h) System admin §6
Kea lease cache 0.0 (off) DHCP §4b
Kea HA Between Kea members with an HA Peer URL; a third becomes a backup peer; DHCPv4 only DHCP §14
DHCP Lease Sync poll 15 s (minimum 10 s) Getting Started §5
IP allocation strategy Sequential System admin §6
Utilization thresholds Warn 80%, critical 95%; IPv4 longer than /29 excluded System admin §6
Bulk allocate Up to 1024 IPs per run CHANGELOG
Discovery sweep Off per subnet; up to /20; IPv4 only IPAM §8
IPAM → DNS Reconciliation Off; hourly when on DNS §14
Zone ↔ Server Reconciliation Off; every 30 min when on DNS §14
Agent heartbeat Every 30 s (±3 s) DNS agent §4
Server marked unreachable After 3 missed heartbeats (90 s) DNS agent §4
Agent token 24 h, rotated within 12 h of expiry DNS agent §6
Agent outage spool 256 MiB (AGENT_SPOOL_MAX_BYTES) Observability §9.1
Config change reaches agents Well under 1 s; at most about 12 s Architecture §4
Alert evaluation Every 60 s Observability §9.1
Maintenance mode Writes get 503 with Retry-After: 120 System admin §5
Request body cap 8 MiB (MAX_REQUEST_BODY_BYTES), larger for backups, images and importers REST API §8
Backup passphrase At least 8 characters; it encrypts secrets.enc only System admin §2.9
Restore drills Refused when the database is over 20 GiB System admin §2.9
Support bundle 48 MB at most System admin §9
Pairing code 8 digits; single-use codes expire after 15 min, persistent codes after 30 days Appliance §9
Appliance disk 32 GiB minimum; 40 GiB SSD for a control plane; 48 GiB per node for a three-node control plane Appliance · Sizing
Appliance control-plane members 1, 3, 5 or 7 Topologies
Docker Compose host 2 vCPU / 4 GiB minimum, 4 vCPU / 8 GiB recommended Docker

All under Settings → Application → Data Retention, superadmin-only, applied by a nightly prune.

Data Default Range
DNS query & DHCP activity logs 24 hours 1–168 hours
DNS & DHCP metrics 7 days 1–365 days
DHCP lease history 90 days 0–3650 days (0 keeps forever)
Trash 30 days 0–365 days (0 never purges)
Packet captures 7 days 1–90 days
Deleted (revoked) Fleet appliances 30 days 0–365 days (0 keeps them until deleted for good)

The audit log has no retention setting: nothing prunes it.

Source: System administration §2.7, data retention (#2273)