Skip to content
Lumaft documentation contents
Lumaft documentation

Deployment and setup

Prerequisites, the container contract, and how to choose a deployment environment.

Deployment and setup

Lumaft ships as one container image. It contains the browser console, the API, and the backend observer. Everything else — storage, TLS, credentials, backups — comes from the environment you deploy it into.

This page lists what every deployment needs and helps you choose an environment. The environment guides then walk through each platform step by step.

Prerequisites

The image

  • One edition-neutral OCI image per release: ghcr.io/dekglas/lumaft (canonical) and docker.io/dekglas/lumaft (mirror). Both resolve the same digest for a given version.
  • Multi-architecture: linux/amd64 and linux/arm64.
  • Runs as the unprivileged node user (UID/GID 1000). Supports a read-only root filesystem when /data and /tmp are writable mounts.
  • Listens on port 8080.
  • Every published digest is keylessly signed. Verify before you pull.

There is no latest tag. Always deploy by digest, taken from the release record (releases/<version>/release.json) in the public distribution repository. Pulling the image needs no registry login; if you mirror it into a private registry, your platform needs that registry's credentials instead.

New to containers? Read Quickstart first. It runs Lumaft on your own machine in about fifteen minutes and explains each moving part once, so the environment guides below make sense.

LUMAFT_VERSION=<version>
LUMAFT_DIGEST=sha256:<digest from releases/$LUMAFT_VERSION/release.json>
LUMAFT_IMAGE="ghcr.io/dekglas/lumaft@$LUMAFT_DIGEST"

cosign verify \
  --certificate-identity-regexp '^https://github\.com/dekglas/application/\.github/workflows/promote-public-release\.yml@' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  "$LUMAFT_IMAGE"

docker pull "$LUMAFT_IMAGE"

Compute

The measured starting point is 1 vCPU and 2 GiB of memory for a SQLite workload of 20,000 operation summaries across 200 stacks. Measure your own peak workload before growing beyond that envelope; if read latency or cold readiness degrades, plan PostgreSQL adoption or requalify your allocation.

Storage

Lumaft keeps its durable record in one of two databases.

Database Edition Replicas Storage requirement
SQLite (default) Community Exactly one An exclusive, durable, block-backed volume mounted at /data
Customer-operated PostgreSQL Business One A PostgreSQL server you run, size, back up, and patch
PostgreSQL with replicas Enterprise Declared ceiling The same server with a declared connection budget

SQLite runs in WAL mode. The /data volume must therefore be:

  • exclusive to one Lumaft replica;
  • presented to the host as local block storage (a disk, an EBS volume, a managed disk, a VMDK);
  • independent of the container's lifecycle, so replacing or scaling the compute to zero does not delete it.

The following are not supported for SQLite: the container writable layer, task-local scratch storage, emptyDir, EFS, NFS, SMB, Azure Files, ReadWriteMany volumes, any volume shared by more than one replica, and ECS service-managed EBS volumes (ECS deletes those with the task).

If your platform cannot provide that volume lifecycle, use PostgreSQL. See Database integration.

Secrets as files

Two inputs are read from files, not environment variables:

File Named by Purpose
Bootstrap administrator password LUMAFT_LOCAL_ADMIN_PASSWORD_FILE Creates the first administrator on first startup only
Backend definitions LUMAFT_BACKENDS_FILE The validated JSON list of backends to observe

Both must be direct regular files below a protected directory: absolute path, not a symlink, parent directory not writable by group or others, file not writable by group or others, readable by UID 1000. Kubernetes-style projected volumes that materialize secrets as symlinks are rejected. The pattern that works on every platform is a small init step that writes the files into a task-local volume mounted at /run/lumaft before the application container starts. Each environment guide shows it.

Why UID 1000 keeps appearing. The process inside the container runs as the node user, whose numeric ID is 1000. Linux checks file permissions by numeric ID, so a file you create on a Linux host as another user is unreadable inside the container until you chown 1000:1000 it. Docker Desktop on macOS and Windows maps ownership for you, which is why the same command works there without the chown and fails on a Linux server with it missing. Every guide on this site includes the chown; do not skip it on Linux.

Backend credentials

Lumaft reads S3 through the standard AWS SDK credential provider chain. Prefer the platform's workload identity — an ECS task role, an EC2 instance profile, a web-identity token — over static keys. The identity needs s3:ListBucket and s3:GetObject on the state namespace and nothing else. Credentials never go in the backends file. See Connect a backend.

Networking

Direction Requirement
Inbound HTTPS, terminated by the environment (load balancer or reverse proxy), forwarded to the container on 8080
Outbound HTTPS to the object store, to the container registry at deploy time, and to the licensing service for connected Business or Enterprise licenses

Lumaft itself does not terminate TLS. Any deployment reachable off the loopback interface must sit behind TLS; the default session cookie is __Host-lumaft-session with the Secure attribute, and browsers will not retain it over plain HTTP. LUMAFT_ALLOW_HTTP=true exists only for a browser on the same machine reaching 127.0.0.1.

Health endpoints

Endpoint Auth Meaning
/api/v1/health Public Process liveness
/api/v1/readiness Public Returns 200 only after a bounded database query succeeds; 503 otherwise
/api/v1/storage-health Administrator Write probe, free space, database file sizes, last durable write

Readiness never contacts a configured backend, so a bad S3 credential does not take the service out of rotation. Use readiness to route traffic and liveness to restart a process. The image's built-in HEALTHCHECK calls readiness every 30 seconds.

Choose an environment

Environment Best for Database Guide
Amazon EC2 The simplest AWS shape: one instance, one volume, one container SQLite or PostgreSQL Set up on AWS EC2
Amazon ECS on EC2 Teams already running ECS who want SQLite on a retained EBS volume SQLite or PostgreSQL Set up on AWS ECS
Amazon ECS on Fargate Teams who want no hosts to manage and can run PostgreSQL PostgreSQL (Business) Set up on AWS ECS
Kubernetes Clusters with a block StorageClass (EKS, AKS, GKE, on-premises) SQLite or PostgreSQL Set up on Kubernetes
Azure Container Apps Azure-native serverless containers PostgreSQL (Business) Set up on Azure
Azure Virtual Machines Azure with SQLite on a managed disk SQLite or PostgreSQL Set up on Azure
VMware vSphere On-premises and private cloud, including air-gapped sites SQLite or PostgreSQL Set up on VMware

On AWS, Deploy with Pulumi stands up either the EC2 or the ECS shape from one program, and IAM setup on AWS covers the roles every AWS shape needs. Every VM-based shape ends on Docker Compose and systemd.

Two rules decide the column that matters most:

  1. SQLite needs an exclusive local block volume. EC2, ECS on EC2, Kubernetes with a block StorageClass, Azure VMs, and vSphere can provide one. Fargate and Container Apps cannot.
  2. PostgreSQL adoption is a Business capability. Continuing to run an adopted database never depends on the license, but starting a new installation on PostgreSQL, or migrating one to it, does.

Configuration reference

All settings are environment variables. Files named by a setting must meet the file rules above. The complete list, including licensing, federated sign-in, capture, and audit settings, is the Configuration reference; the tables below cover what a first deployment needs.

Core

Variable Default Purpose
PORT 8080 Listening port
LUMAFT_SQLITE_PATH /data/lumaft.db SQLite database path; parent must exist with mode 0700
LUMAFT_BACKENDS_FILE unset Backend definitions; unset starts with no backends
LUMAFT_LOCAL_ADMIN_PASSWORD_FILE unset Bootstrap administrator password; remove after first startup
LUMAFT_LOCAL_ADMIN_USERNAME admin Bootstrap administrator username, set before first startup
LUMAFT_LOCAL_ADMIN_PASSWORD_ROTATE false One-shot lost-password rotation; see Users and permissions
LUMAFT_PUBLIC_ORIGIN unset Externally reachable HTTPS origin; required for OIDC and SAML
LUMAFT_ALLOW_HTTP false Loopback-only evaluation exception; never set off the loopback interface

Database

Variable Default Purpose
LUMAFT_POSTGRES_URL unset PostgreSQL connection URL for a fresh installation
LUMAFT_POSTGRES_URL_FILE unset Same, read from a protected file (preferred)
LUMAFT_REPLICAS_MAX 1 Maximum simultaneous replicas (Enterprise, PostgreSQL only)
LUMAFT_POSTGRES_POOL_MAX 1 Pooled connections per replica
LUMAFT_POSTGRES_CONNECTION_HEADROOM 10 Connections reserved for your other clients
LUMAFT_MIGRATION_WAIT_SECONDS 300 How long a replica waits for the migrating replica
LUMAFT_SCHEMA_UPGRADE_APPLY unset Authorizes one named gated migration; remove afterwards
LUMAFT_SCHEMA_UPGRADE_RECOVERY_DIRECTORY beside the database Where the pre-migration recovery point is written

Licensing

Variable Purpose
LUMAFT_LICENSING_SERVICE_URL Connected licensing endpoint for Business and Enterprise
LUMAFT_CONNECTED_LICENSE_DIR Local connected-license state directory
LUMAFT_OFFLINE_SITE_LICENSE_FILE Enterprise Advanced Security offline site license

Optional capture and audit

Variable Purpose
LUMAFT_TRANSCRIPT_KEY_FILE 32-byte base64 key for transcript encryption, outside /data
LUMAFT_TRANSCRIPT_REDACTION_FILE Additional redaction rules (JSON, ≤ 64 KiB)
LUMAFT_STATE_KEY_FILE Key for opt-in whole-state retention
LUMAFT_AUDIT_SINK_URL, LUMAFT_AUDIT_SINK_ID, LUMAFT_AUDIT_SINK_SECRET_FILE Enterprise external audit sink

Federated sign-in variables (LUMAFT_OIDC_<KEY>_*, LUMAFT_SAML_<KEY>_*, LUMAFT_TRUSTED_PROXY_<KEY>_*) are documented in Users and permissions.

The reference docker run, annotated

Every environment guide is a variation of this command. Read it once here; the guides then only show what changes.

mkdir -p run && chmod 700 run
printf '%s\n' "$(openssl rand -base64 24)" > run/admin-password   # 12–4096 characters
cp backends.json run/backends.json                                 # see Connect a backend
chmod 600 run/*
sudo chown -R 1000:1000 run                                        # Linux hosts only

docker volume create lumaft-data

docker run --name lumaft \
  --detach \
  --init \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --publish 127.0.0.1:8080:8080 \
  --mount type=volume,source=lumaft-data,target=/data \
  --mount type=bind,source="$PWD/run",target=/run/lumaft,readonly \
  --env LUMAFT_LOCAL_ADMIN_PASSWORD_FILE=/run/lumaft/admin-password \
  --env LUMAFT_BACKENDS_FILE=/run/lumaft/backends.json \
  --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --env AWS_SESSION_TOKEN \
  --env LUMAFT_ALLOW_HTTP=true \
  "$LUMAFT_IMAGE"

curl -fsS http://127.0.0.1:8080/api/v1/readiness
Line What it does If you drop it
--init Runs a minimal init process so signals reach Lumaft and it can shut down cleanly docker stop may wait for the timeout and leave WAL sidecars behind
--read-only + --tmpfs /tmp Makes the image's filesystem immutable; only /data and /tmp are writable The container still runs, but with a larger writable surface than it needs
--publish 127.0.0.1:8080:8080 Exposes port 8080 on the loopback interface only Publishing on 0.0.0.0 exposes plain HTTP to the network
--mount type=volume … /data A Docker-managed volume for the database, independent of the container The database is lost when the container is removed
--mount type=bind … /run/lumaft The directory holding the password and backends files, read-only Startup refuses: neither file can be found
--env AWS_* Passes your shell's AWS credentials through to the container (the name alone forwards the value) Readiness stays green, but the backend diagnostic fails and no stacks appear
--env LUMAFT_ALLOW_HTTP=true Lets a browser on this machine keep the session cookie over http://127.0.0.1 Sign-in appears to succeed and immediately returns you to the sign-in screen

LUMAFT_ALLOW_HTTP=true is for this loopback-only shape. Every network-reachable deployment terminates TLS in front of the container and leaves it unset. Never mount the Docker socket into the container and never run it privileged.

After the container starts

  1. Confirm /api/v1/readiness returns 200.
  2. Sign in as the bootstrap administrator and change nothing else yet.
  3. Unset LUMAFT_LOCAL_ADMIN_PASSWORD_FILE and securely delete the password file.
  4. Open Administration → Backends and confirm each backend's diagnostic result.
  5. Take your first cold backup once the first observation cycle has committed.

Then follow Your first hour to work through the first-run readiness checklist and find your way around the console.

ScreenshotThe Lumaft sign-in screen, showing the username and password fields and the link to the EULA at /legal/eula

Production checklist

  • Image pulled by verified digest, never by tag
  • /data on an exclusive block volume, or PostgreSQL configured
  • Password file and backends file mounted as direct regular files, mode 0600, owned by UID 1000
  • Read-only backend credentials with tested least privilege
  • TLS terminated in front of the container; LUMAFT_ALLOW_HTTP unset
  • Readiness wired into the load balancer; liveness wired into the restart policy
  • Cold backup procedure tested, including a restore
  • Storage-health alerts on writeProbe.outcome, dataDirectory.freeBytes, and lastDurableWrite