Skip to main content

Deployment overview

Who it’s for
DevOps engineers who run, upgrade or debug a Lucia install.
What it covers
The machines, containers, ports and state directories, with commands to check each.
Why it exists
Lucia is one Docker Compose stack on a DGX Spark plus a small agent on every managed server. Knowing that layout is most of operating it.

Topology​

One Spark is the controller. Managed servers are x86-64 machines that Lucia installs with Debian 13. The Spark runs everything else.

The compose file is generated by Aspire from the Lucia.Homelab.Identity.AppHost project and written for the project name lucia-identity. The identity stack and the Lucia host share that one file, so one compose up covers both. See the code map for how the pieces call each other.

Containers on the Spark​

ContainerImageRole
lucia-homelab-hostlucia-managed-host (built on the Spark, never pulled)API, portal, in-process inference, assistant, controllers. Runs as the setup user, CapDrop: ALL, GPU through CDI
lucia-identity-gatewaytraefik:v3.6The only TLS entry. Waits until certificates verify before it starts
lucia-identity-server, lucia-identity-workergoauthentik/server:2026.8.3OIDC sign-in
lucia-identity-ldapvegardit/openldapDirectory. Plain LDAP on 389 stays inside the Docker network
lucia-identity-dbpostgres:16-alpineAuthentik's database, not published
lucia-identity-casmallstep/step-ca:0.30.2Private certificate authority
lucia-identity-renewersmallstep/step-ca:0.30.2Renews the gateway certificate
lucia-telemetry-relay, lucia-telemetry-node-exporter, lucia-telemetry-gpu-exporterOpenTelemetry Collector 0.161.0, node-exporter v1.10.2, nvidia_gpu_exporter 1.15.1Host network, loopback listeners only
lucia-bootlucia-boot (built locally)TFTP and HTTP for network install. Present only when boot settings exist

Every container is labelled io.lucia.component and has restart: unless-stopped and no-new-privileges. The host container never mounts the Docker socket. It gets a few read-only /proc files for health sampling, its data and model directories, and the public CA certificate.

Ports​

PortProtocolServed byNotes
443TCPGateway to lucia-host:8080Portal, /api, /v1. Must match PublicOrigin
9443TCPGateway to AuthentikDefault, set at provisioning
636TCPGateway to LDAPLDAPS for managed servers. Default
9444TCPstep-ca, published directlyPrivate CA. Default
8445TCPGateway public entrypointOnly admits Cloudflare ranges. Used when public access is on
69, 40000-40016UDPlucia-bootBound to the boot address only
9080TCPlucia-bootDefault http_port, on the boot address

The three identity ports are distinct and stored in settings.json. Provisioning refuses 443 as an identity port. Nothing else is published: PostgreSQL 5432, Authentik 9000 and LDAP 389 stay on the Docker network, and the telemetry exporters bind to 127.0.0.1.

State directories​

Everything lives under the setup user's home on the Spark.

PathHolds
~/.local/share/lucia/bootstrapUploaded desktop payload, job records, pinned tools (.NET, Aspire)
~/.local/share/lucia/identitysettings.json, ca/, certificates/, config/, secrets/, trust/, gateway/, ldap/data, ldap/config, boot-settings.json
~/.local/share/lucia/host/dataMounted as /data in the host: data-protection, inference-keys, provider-credentials, network-credentials, domains, onboarding, packages, assistant, telemetry, boot-control, nodes, huggingface
~/.local/share/lucia/host/modelsMounted as /models. Model weights
Docker volumes lucia-identity-postgres, lucia-authentik-data, lucia-authentik-mediaAuthentik's data
/var/lib/lucia/packagesRoot package worker state

identity/ca and identity/secrets hold the CA keys and every bootstrap secret. Back them up encrypted and never copy them to another machine casually. A restore needs all of identity state, the host data directory and the three volumes, in the same version set.

danger

Never run aspire destroy against a live install. Teardown can remove the Compose volumes that hold PostgreSQL and Authentik data. Aspire also derives Compose project and volume names from the AppHost path, so keep that path stable across upgrades.

Spark-side services​

Work that needs more privilege than the host container runs outside it, as systemd units that read file queues in /data.

UnitScopeDoes
lucia-domain-activation.serviceuserApplies a reviewed domain profile and restarts the stack
lucia-node-enrollment.serviceuserEnrolls managed nodes against the directory and CA
lucia-spark-model.service, lucia-spark-runner.serviceuserOptional Spark model and on-demand GitHub runner
lucia-skill-runner.serviceuserRuns Spark-mode assistant skill scripts in throwaway containers; builds its own image on first start
lucia-package-updates.servicerootA fixed list of apt actions. Installed by hand with sudo

User units need lingering, or they stop at logout and the portal reports domain, people and enrollment work as blocked:

loginctl show-user "$USER" -p Linger --value # expect: yes
loginctl enable-linger "$USER"

The root worker is installed on purpose by an operator:

sudo python3 ~/.local/share/lucia/bootstrap/app/tools/packages/package_worker.py install

Managed servers​

Each server runs the node agent as root under systemd, plus whatever Docker stacks you install. It sends a heartbeat every 30 seconds over HTTPS and proves its identity with a certificate from the private CA plus a signature over a fresh challenge. See Nodes and Installer.

Verify health​

From any machine that trusts the private CA (or add -k for a one-off):

curl -fsS https://lucia.example.com/health/live # {"status":"ok"}
docker ps --filter label=io.lucia.component --format 'table {{.Names}}\t{{.Status}}'
docker inspect lucia-homelab-host --format '{{.State.Health.Status}}'

/health/live is the only unauthenticated path the origin check lets through. The /health and /alive endpoints from the service defaults exist only when ASPNETCORE_ENVIRONMENT is Development.

Every compose service has a healthcheck, so an unhealthy container shows in docker ps. If lucia-homelab-host is unhealthy, read its log first:

docker logs --tail 100 lucia-homelab-host

Startup validation errors name the setting at fault, for example HostAuthentication: ClientSecretFile cannot be read. Configuration lists them.

Where to go next​