Architecture
Firstlume is one server backed by Postgres, plus agents that create a short-lived Kubernetes Job for each phase of a run.
┌──────────────────────────────┐ ┌──────────┐
user ─────▶│ server (API + UI) │◀────▶│ Postgres │
(UI/API) │ /api/v1 users (admin) │ └──────────┘
│ /agent/v1 agents │
│ /exec/v1 executors │
│ /state/v1 state (Terraform) │
└──────▲───────────────▲───────┘
│ claim (pull) │ spec, logs, plan, result
┌──────┴───────┐ │ (token valid for 1 phase only)
│ agent │─creates─▶ Job "flr-<run>-plan" ─┘
└──────────────┘ └─ firstlume exec
One binary, one image
The subcommands are firstlume server | agent | exec | migrate | keygen. The React UI is embedded in the binary, and the server serves it at the same address as the API.
The agent pulls work
The agent only makes outbound requests. It can run in another cluster or on a client's network without the API having to reach it. An agent belongs to one organization and only receives that organization's runs. See An additional agent in another cluster.
Each phase runs in a short-lived Job
Plan and apply each get their own Job. The pod is hardened:
- it doesn't run as root
- its root filesystem is read-only
- it can't escalate privileges
- the Job is never retried
Cloud credentials come from the pod's identity (IRSA, Workload Identity), through annotations on the executor's ServiceAccount. You can choose the nodes where Jobs run.
The Job's token is valid for a single phase. Each phase also gets a state credential, valid only while the phase is active and only for that stack.
One run at a time, per stack
Only one run at a time executes or waits for approval on each stack. The others wait in a queue, in order of arrival. Two stacks from the same repository have independent locks and queues.
Apply runs exactly what was planned
The plan records the commit SHA and the plan file, encrypted in the database. The apply checks out that commit and applies that file, with the variables the plan used. See Runs and approval.
Encrypted secrets
Sensitive variables, Git tokens and plans are encrypted with AES-256-GCM, using a key derived from FIRSTLUME_SECRET_KEY. State versions are encrypted too, with a key of their own. Sensitive values are masked in logs and never returned by the API.
Vault secrets are different: they never reach the server or the database, because the executor reads them inside the run's pod. See Vault secrets.
State
Firstlume serves the Terraform http backend at /state/v1. Metadata and locks always live in Postgres. The state versions themselves live in Postgres or in an S3 bucket. See State backend and Where state lives.