Skip to content
hekla

Running a project

hekla serve, what it creates, what it refuses to start without, and every configuration option.

hekla serve .
hekla listening on http://127.0.0.1:8080
  admin console   http://127.0.0.1:8080/admin
  api reference   http://127.0.0.1:8080/docs

One binary is the whole runtime. It serves the API, keeps the read models, runs the effects and holds the keys, with tephra embedded as a library and SQLite bundled. There is nothing to stand up beside it and nothing to point it at.

Arguments

Default
DIR . the project directory
--addr 127.0.0.1:8080 the HTTP bind address
--data-dir <dir>/data event store and operational database
--verify off run the continuous invariant check

--verify can only turn verification on. A [verify] enabled = true in hekla.toml cannot be turned off by leaving the flag out.

What happens at startup

In order, because the order is what makes some failures loud and some impossible:

  1. The metrics recorder is installed, before anything else, so no counter is dropped.
  2. The project is loaded and checked. Any error refuses the start: refusing to serve: the project has N error(s).
  3. The bind address is parsed.
  4. Master keys are read from the environment.
  5. The data directory is opened and the exclusive lock is taken.
  6. Declared secrets are resolved. A required one this machine cannot supply refuses the start, before anything is recorded about this deploy.
  7. Projectors and effects start, and the HTTP server binds.

Two of those refuse rather than degrade, and deliberately.

A missing master key. If stored subject data was wrapped under a key that is not configured now, the process fails at boot naming the missing key ids, rather than starting and serving nulls where personal data used to be.

A missing required credential. A declared secret this machine cannot supply stops the start. hekla secrets is the same check without starting anything, which is what makes it a pre-deploy gate.

Shutdown

Ctrl-C drains in a fixed order: effects first, then projectors, then the writer. An invocation still in flight after 30 seconds is abandoned; it stays marked running and replays from its journal at the next start, so nothing it already did is repeated.

One process per data directory

data/hekla.lock.db is an exclusive lock. Which subcommands take it is worth knowing, because it decides what you can run against a live server:

Takes the lock Does not
serve plan
verify erase
rewind rotate

So you can run hekla plan and hekla erase against a running deployment, and you have to stop the server for hekla verify and hekla rewind.

hekla.toml

Optional, at the project root. A missing file means every default below. Every key is validated on load and an unknown key is an error, so a misspelling is caught rather than silently ignored.

[effects]
# How many lanes run at once, process-wide. 1 to 1024.
pool_size = 16

[retention]
# How long a completed effect invocation's journal is kept.
effect_journal_days = 7

[projectors]
# Rebuild a projector automatically when its definition hash changes.
auto_rebuild = true

[verify]
# Replay every completed invocation against a sealed journal as it runs.
enabled = false

[secrets]
DISCORD_WEBHOOK = { env = "DISCORD_WEBHOOK_URL" }
STRIPE_KEY = { file = "/run/secrets/stripe" }

A [secrets] entry takes env or file and nothing else. There is deliberately no third form carrying the value, and { value = "sk_live_..." } is a parse error rather than a convenience, because writing a credential into the source tree is the exact mistake the feature exists to prevent. A relative file path resolves against the project root.

Environment

Variable Read by Default
HEKLA_MASTER_KEY serve, verify, rotate, plan --replay none; base64 of 32 bytes
HEKLA_MASTER_KEY_PREVIOUS the same none; comma-separated prior masters
HEKLA_SECRET_<NAME> serve, verify, plan, secrets fallback for a credential not named in [secrets]
HEKLA_MAX_ATTEMPTS serve 5, capped at 15; the command conflict-retry budget
HEKLA_UI_DIR serve none; serve console assets from disk
RUST_LOG serve, verify info
NO_COLOR serve, verify unset; any non-empty value drops ANSI

HEKLA_MAX_ATTEMPTS is read once per process. An unparseable value falls back to the default rather than failing.

HEKLA_SECRET_<NAME> is never read by check or test, so neither needs any credential.

What is not configurable

Named here because the absence is deliberate and people go looking:

  • HTTP connect (10s) and total (30s) timeouts for outbound calls
  • effect retry backoff, from 200 ms doubling to a 60 s cap
  • the read-your-writes wait default (5s) and maximum (30s)
  • the journal sweep interval, hourly
  • the shutdown drain timeout, 30s
  • metrics collection: every gauge is read at scrape time, so there is no interval to set

Generating a master key

HEKLA_MASTER_KEY=$(head -c 32 /dev/urandom | base64)

Keep it somewhere you will still have it next year. Losing it is permanent, unrecoverable loss of every subject-scoped value in the log, and nothing else in the runtime fails that way. See Keys and secrets.