Reference
orca.toml
orca.toml sits at the root of a project's repository and says how each
pipeline runs: where, on what, how much it uses and how it fills. The pipeline's code
still says what it does. orca register reads the file, checks every key,
and records each value with where it came from.
An example
[defaults]
placement = "ec2"
head_type = "t4g.medium"
[pipelines.gharchive]
worker_type = "c7g.xlarge"
workers = "0..4"
max_runs = 2
env = { DISTA_WORKER_SLOTS = "4", GHARCHIVE_PARALLEL = "16" }
fills = "all" # fill the whole range by itself
fill_max_runs = 2
fill_budget_per_day = 5.0 # estimated US dollars a UTC day
fill_window = "22..6" # UTC hours fill runs start in
fill_lag = "2h" # a day fills 2 hours after it ends
[pipelines.mojo_repos]
placement = "worker:lab"
secrets = { GITHUB_TOKEN = "orca/github-token" }
cpus = "1..2" # starts once 1 CPU and 0.5 GB fit,
memory_gb = "0.5..2" # and is given up to 2 of each
[pools.lab]
provider = "hetzner"
machines = [{ type = "cax31", wait = 600 }, { type = "cpx32" }]
locations = ["hel1", "nbg1"]
max_machines = 3 The full reference, with every rule, is docs/orca-toml.md; the code that reads the file is config.mojo and pool_config.mojo.
Tables
| Table | What it is |
|---|---|
[defaults] | values for every pipeline the file names; pipelines the file leaves out don't get them |
[pipelines.<name>] | one pipeline's own values, by the name its describe gives; an empty table puts the pipeline in the file with [defaults] |
[pools.<name>] | a pool of remote workers that morca grows and shrinks itself |
[apps.<name>] | from 0.26.0: a long-running binary of the image beside the UI, such as the census console, and what the stack gives it |
[stack] | reserved for orca aws up, which doesn't read it yet; it is accepted and not checked |
Any other table, and any key at the top level, is refused. morca reads the part of
TOML 1.0 a config needs: comments, dotted and quoted keys, basic and literal strings,
decimal integers, floats, booleans, arrays (over several lines, with a trailing
comma), inline tables and arrays of them. Multi-line strings, dates,
[[arrays of tables]], hexadecimal, octal and binary integers,
inf and nan are refused with a message that says so, as is
a key or table defined twice.
[pipelines.<name>]
Every key below can go in [defaults] or in a pipeline's own table. A
value comes from the first of these that sets it: a flag to orca register
(recorded as an override), the pipeline's table, [defaults], the
environment, then the code. env and secrets merge one name at
a time. image and --spot stay flags only, and no flag sets
the fill* keys or learn.
Placement and size
| Key | Type | Default | Meaning |
|---|---|---|---|
placement | "default", "control", "ec2" or "worker:<pool>" | default (the scheduler's ORCA_RUNNER) | Which machine starts the pipeline's runs. control is the scheduler's host; worker:<pool> is a remote worker of that pool. |
head_type | string, an instance type | ORCA_EC2_HEAD_TYPE, else c7g.large | The head instance's type, used only on EC2. |
worker_type | string, an instance type | ORCA_EC2_WORKER_TYPE, else c7g.xlarge | The worker instances' type, used only on EC2. |
workers | integer N (at least 1), or "MIN..MAX" | the code's workers, else 2; 0 without tasks | Dista workers a run gets. With a range the run scales between the two. On worker:<pool>, the extra machines one run may spread over. |
worker_procs | integer 1 to 256, or "auto" | 1 | Dista processes per worker machine; auto is one per vCPU. |
max_runs | integer 1 to 10000, "auto" or "auto..N" | the code's max_runs, else 1 | Live runs of the pipeline at once, overriding the code. auto follows the host's room, up to 64 or N. |
keys_per_run | integer 1 to 10000 | the code's keys_per_run, else 1 | Keys one run takes: a backfill's runs, a sensor's runs above 1, and fill runs. |
placement.md describes the four
placements, and Remote workers covers
worker:<pool>.
Requests
| Key | Type | Default | Meaning |
|---|---|---|---|
cpus | integer 1 to 1024, "MIN..MAX", "auto" or "auto..MAX" | none: no request | Whole CPUs a run needs to start and can use. auto takes the request learned from its runs. |
memory_gb | number over 0 up to 65536, "MIN..MAX", "auto" or "auto..MAX" | none: no request | GB of memory a run needs to start and can use; decimals are allowed. |
learn | boolean | true | false keeps auto at no request, though the recommendation is still shown. |
Fills
| Key | Type | Default | Meaning |
|---|---|---|---|
fills | "all" or "since_registration" | since_registration | all makes the scheduler fill the pipeline's whole range by itself. |
fill_order | "oldest" or "newest" | oldest | Which end of the range fills first. |
fill_max_runs | integer 1 to 10000; "auto" or "auto..N" from 0.24.0 | 1 | Fill runs at once, on top of max_runs. auto follows the host's room, up to 64 or N. |
fill_budget_per_day | number 0 to 100000, US dollars | none | Estimated spend of fill runs per UTC day. Fill runs on EC2 or Kubernetes don't start without it. |
fill_window | "A..B", UTC hours: A 0 to 23, B 1 to 24 | "0..24", all day | The hours fill runs may start in. "22..6" wraps past midnight. |
fill_lag | a duration: whole s, m, h or d up to 31d, or 0 | 0 | How long after its end a partition is filled, for sources that publish late. |
Secrets and environment
| Key | Type | Default | Meaning |
|---|---|---|---|
secrets | table of NAME = "orca/<id>" | none | Secret IDs, never values, read when a run starts and passed as NAME. A --secret flag replaces the whole set. |
env | table of NAME = "value" | none | Variables for this pipeline's runs only, on every runner. Numbers and booleans become text; "auto" sizes from the grant. |
How many at once
max_runs = "auto" starts another run of the pipeline whenever its
request fits in what this host's running runs weren't granted, up to a ceiling of 64;
"auto..N" sets the ceiling to N. A bigger host then runs more at once with
no change to the file: at 2 GB a run, 4 on a host with 8 GB to grant, 12 on one with
24.
[pipelines.gharchive]
memory_gb = "auto" # learned from its runs
max_runs = "auto"
fill_max_runs = "auto" # from 0.24.0
It needs a request to weigh, declared or learned. Until it has one, it starts one run
at a time, and its queued runs say so. On EC2, Kubernetes and remote workers, where
there is no host's room to weigh, the ceiling is the limit; a worker still admits what
it takes against its own host. From 0.24.0, fill_max_runs = "auto" does
the same for fill runs (migration 55 records it as
orca.pipelines.fill_max_runs_auto). An older morca reads
"auto" as its ceiling.
Requests and grants
cpus and memory_gb are a request: the least a run needs to
start and the most it can use (a single number is both). On the local runner, for
control-placed runs and on remote workers, a run starts only if its minimum fits what
the host can grant, less what the runs going there were granted. The host's grant is
its CPUs and memory (its cgroup's limits if set, or ORCA_HOST_CPUS and
ORCA_HOST_MEMORY_MB) less ORCA_RESERVE_CPUS (1) and
ORCA_RESERVE_MEMORY_MB (1536); a worker's reserve is 0 and 512. A run
that starts gets up to its maximum of what's left, fixed for that start, in
ORCA_GRANT_CPUS and ORCA_GRANT_MEMORY_MB.
- A pipeline without a request runs as before, unchecked and not counted against the others, so on a shared host give every pipeline one.
- On EC2 and Kubernetes the request is kept but not used yet.
- Where morca can make cgroups, a run's grant is its memory limit, and the kernel
kills it
ORCA_RUN_MEMORY_MARGIN(25) percent above that. Its retry and later runs then ask forORCA_RUN_MEMORY_GROWTH(1.5) times as much, up tomemory_gb's maximum. "auto"takes the request from the pipeline's last 20 successful measured runs: memory at their 95th percentile plus 15%, CPUs at the 90th percentile of peak cores. With fewer than 3 such runs there is no request yet."auto..24"caps it at 24.- An
envvalue"auto"lets the run size itself from its grant (orca.auto_count).DISTA_WORKER_SLOTS = "auto"becomes the granted CPUs over the run's local Dista workers, or 0 (one per core) without a grant.
Scheduling and running explains this in prose, and orca-toml.md has every rule.
Memory pressure
On top of max_runs, fill_max_runs and grants, no new run
starts on the local runner, the control host or a worker while Linux's pressure stall
information reads some avg10 over ORCA_ADMIT_MEMORY_PRESSURE
(10%; 0 turns it off). Runs already going go on. The hold ends under half the limit,
or after 30 s at most at the limit. One that lasts
ORCA_ADMIT_PRESSURE_GRACE_SECONDS (600) with none of morca's runs going
raises the memory-pressure alert and lets runs start one at a time.
Without PSI, as on macOS, nothing is held. These are environment variables, not
orca.toml keys.
env and secrets
- A pipeline's
envgoes only to its own runs: their process environment on the local runner, the control host and a worker, each pod'senvon Kubernetes, and each instance's script and containers on EC2. ORCA_RUN_ENVstill copies the scheduler's named variables to every run; a pipeline'senvwins for a name it sets.- On EC2 the env is part of the warm pool's key, so changing it launches new instances.
- Stack settings (database, warehouse, logs, runner, budget) stay in the
scheduler's environment file, not in
env. secretsare read withORCA_SECRET_FETCHwhen it is set (<command> <id>throughsh -c), else from Secrets Manager. IDs always start withorca/.
Fills
With fills = "all", on every tick and after the pipeline's other runs
have started, the scheduler queues fill runs over its range: its first partition
through the last one that ended at least fill_lag ago. Each fill run
takes keys_per_run partitions in fill_order, with trigger
fill. A fill never runs a partition twice: one done in any of the
pipeline's code, in a live run, or ever queued as a fill is taken. Fill runs don't
count against max_runs, never start while other runs of the pipeline are
queued, and on a worker pool are left for the pool's workers to claim, with
fill_max_runs across the whole pool.
The cost estimate fails closed: an hour of the head plus the most workers at
on-demand prices, times 1.2, against today's spend on fill runs' instances. Runs on
the control host or a local process cost nothing and need no budget. Keyed pipelines,
sensors and pipelines with no first partition can't fill, and orca register
refuses fills = "all" for them. The pipeline page's Fill line and
fill in its JSON say why a fill is idle:
| Idle because | Meaning |
|---|---|
complete | every partition of the range is done, or the last ones are running |
paused | the pipeline is paused, by hand or by the watchdog, whose rule the reason names |
outside its window | the UTC hour is outside fill_window; a fill run already going goes on |
no budget | the runs cost money (EC2 or Kubernetes) and fill_budget_per_day isn't set |
price unknown | orca.fleet's price table can't price the run, or it runs on Kubernetes; the estimate fails closed |
budget | today's estimated spend plus the next run's would go over fill_budget_per_day; it starts again at 00:00 UTC |
waiting | other runs of the pipeline are queued, and they start first |
at fill_max_runs | as many fill runs are going as fill_max_runs allows |
[pools.<name>]
A [pools.<name>] table makes a pool of remote workers that orca
grows and shrinks itself. Pipelines run on it with
placement = "worker:<name>", as on workers started by hand.
orca register records the file's pools in orca.pools; a pool
the file no longer has is deleted there, and its machines go once idle.
[pools.lab]
provider = "hetzner"
machines = [
{ type = "cax31", wait = 600 }, # prefer this; keep trying it for 10 min
{ type = "cax21", wait = 600 },
{ type = "cpx32" }, # the last: no wait
]
locations = ["hel1", "nbg1", "fsn1"]
max_machines = 3
idle_seconds = 900 | Key | Type | Default | Meaning |
|---|---|---|---|
provider | "hetzner" or "ec2" | required | Where the machines are made. |
machines | list of { type, wait } | required | Machine types in order of preference. wait (0 to 86400) is how many seconds morca keeps trying a type before the next; every entry but the last needs one. |
locations | list of strings | none (required on Hetzner) | Where each type is tried, in order: Hetzner locations, or EC2 subnet IDs (none: the default subnet or ORCA_EC2_SUBNET). |
max_machines | integer 1 to 100 | 1 | The most machines the pool has at once. |
idle_seconds | integer 10 to 86400 | 900 | How long a machine may sit idle, with nothing queued for the pool, before it's removed. |
- Grow. While runs of the pool's pipelines have waited 20 s that a
worker could start, no machine is still booting and the pool has fewer than
max_machines, the leading scheduler makes one. It tries the current type in each location in turn for itswait, then the next type, and skips a type whose architecture the worker image lacks. From 0.24.0, a type the provider refuses for the project's quota (Hetzner'sresource_limit_exceeded) moves it on to the next usable type at once. - Boot. A machine holds only a worker token of its own and asks
/api/worker/bootfor the image, its login and the bucket's variables. From 0.24.0 machines are namedorca-pool-<pool>-<n>. - Shrink. A machine idle for
idle_secondswith nothing waiting is removed, its token revoked first. So is one whose worker never shows up (15 min) or goes silent (5 min), and one with the pool's labels that morca doesn't know. - A running machine is never replaced by a type higher in the list: the order only matters when morca adds one.
What the scheduler and the UI need in their environment for pools:
| Variable | What it sets |
|---|---|
ORCA_HCLOUD_TOKEN, ORCA_HCLOUD_TOKEN_SECRET | Hetzner's API token for the scheduler, or the secret that holds it. |
ORCA_HCLOUD_FIREWALL, ORCA_HCLOUD_SSH_KEYS, ORCA_HCLOUD_IMAGE | the firewall and SSH keys (by name) each Hetzner machine gets, and its OS image (ubuntu-24.04). |
ORCA_HCLOUD_NETWORK, ORCA_HCLOUD_NETWORK_ZONE, ORCA_HCLOUD_NETWORK_RANGE | the private network every Hetzner machine joins (orca-<stack id> in eu-central over 10.77.0.0/16 unless set; none for none). |
AWS_REGION and credentials, or an instance role | EC2's API, for provider = "ec2". |
ORCA_POOL_EC2_SECURITY_GROUP, ORCA_POOL_EC2_PROFILE | each EC2 machine's security group and instance profile, else ORCA_EC2_SECURITY_GROUP and ORCA_EC2_PROFILE. |
ORCA_POOL_EC2_AMI_ARM64, ORCA_POOL_EC2_AMI_AMD64 | the EC2 image per architecture, else Ubuntu 24.04 from Canonical's public SSM parameter. |
ORCA_WORKER_IMAGE | the image pool machines run, the server's own (scheduler and UI). |
ORCA_UI_URL, or ORCA_POOL_REMOTE | where pool machines reach the UI. |
ORCA_REGISTRY_USER, ORCA_REGISTRY_TOKEN_SECRET | the login for a private image: the UI hands it to a booting machine, and the scheduler reads the image's architectures with it. |
ORCA_POOL_SCALE_SECONDS (15), ORCA_POOL_GROW_AFTER (20), ORCA_POOL_TRY_SECONDS (60) | how often the autoscaler looks, how long runs wait before it grows, and the time between tries. |
ORCA_POOL_BOOT_SECONDS (900), ORCA_POOL_LOST_SECONDS (300), ORCA_POOL_IMAGE_SECONDS (600) | how long a machine has to join, how long a worker may go silent, and how often the image's architectures are read. |
Pools and autoscaling covers running them.
[apps.<name>]
From 0.26.0, an app is a long-running binary of the project's image,
/opt/morca/bin/<name>, that runs beside the UI: the census console is
one. Its table says what the stack gives it, as a pipeline's table says how its runs
run. Which apps a stack runs is the stack's choice (Apps
on the deploy page).
[apps.console]
port = 8080 # $PORT
check = "/healthz" # 200 once it's ready
env = { CONSOLE_SEMANTIC = "1" }
lake = true # the warehouse and the stack's S3 keys
secrets = { POSTHOG_KEY = "orca/posthog-key" } # an ID, never the value
memory_gb = 1 | Key | Type | Default | Meaning |
|---|---|---|---|
port | integer 1 to 65535 | none: the stack's APPS must give one | Where the app listens, passed as PORT; the tunnel's route points there. |
check | string, a path | / | Answers 200 once the app is ready. A new app serves only then, and the old one keeps serving until it does. |
env | table of NAME = "value" | none | Its variables, one line each. Not PORT, the lake's names, or a name that looks like a secret's (PASSWORD, SECRET, TOKEN). |
lake | boolean | false | true gives it the warehouse settings (MORCA_WAREHOUSE, MORCA_PUBLIC_WAREHOUSE, AWS_REGION, AWS_ENDPOINT_URL_S3) and the stack's S3 keys. |
secrets | table of NAME = "orca/<id>" | none | Each secret's value, passed as NAME. The file holds IDs only, never values. |
memory_gb | number over 0 | none: no limit | Its memory limit (docker --memory). |
- Nothing it doesn't ask for. An app never gets morca's database,
and gets the lake only with
lake = true. - Names. Lower-case letters, digits,
_and-, and not one of the stack's own containers (scheduler,ui,sampler,cloudflared,postgres). - Overlays. A stack's overlay can change an app the file declares, but can't add one.
morca config apps [<file>] [--overlay F] prints the apps one fact
per line, as the stack's script reads them, with secrets by ID:
$ morca config apps
console port 8080
console check /healthz
console lake true
console memory_mb 1024
console env CONSOLE_SEMANTIC=1
console secret POSTHOG_KEY orca/posthog-key What is refused
Each refusal names the file and line, and nothing is registered:
orca.toml:7: [pipelines.gharchive] has no key workrs (it has placement, head_type, ...)
orca.toml:3: [pipelines.toy] workers: not a count or a range MIN..MAX: 4..2 - Unknown keys and tables, and a value of the wrong type.
- A
workers,cpusormemory_gbrange like"4..2";cpus = 0,cpus = 1.5ormemory_gb = 0;autoanywhere but alone or as a range's lower end, or"auto.."without its maximum. - A secret ID outside
orca/. - A
fill_windowlike"6..6"or"1..30", a negativefill_budget_per_day, orfill_max_runs = 0. - A
fill_lagwithout a unit, with an unknown one ("2w"), or over31d. fills = "all"for a keyed pipeline, a sensor, or one with no first partition.- An
envname that isn't a valid variable name, that looks like a secret (*TOKEN*,*SECRET*,*PASSWORD*,*DATABASE_URL*), or that morca sets itself:ORCA_DATABASE_URL,ORCA_IMAGE,ORCA_SECRETS,ORCA_WORKERS,DISTA_ROLE,DISTA_HEAD,DISTA_ADVERTISE,ORCA_GRANT_CPUS,ORCA_GRANT_MEMORY_MB,ORCA_MEMORY_PRESSURE_FILE. - A pool without
providerormachines, a provider other thanhetznerorec2, a machine entry with a key other thantypeandwait, a type that isn't 1 to 64 ofa-z,0-9,.,-and_, a type listed twice, a missingwaiton any entry but the last, a Hetzner pool withoutlocations, ormax_machinesoridle_secondsout of range.
How orca register reads it
| How | File read |
|---|---|
orca register --config F | F |
ORCA_CONFIG=F | F |
neither | ./orca.toml, if it exists |
--no-config | none |
- A pipeline the file names is registered with exactly what the file and flags say. A key removed from the file goes back to its default, and a flag applies to that registration only.
- A pipeline the file doesn't name keeps what was registered for
a flag left out, and
orca registerwarns that it isn't in the file. - Without paths,
orca registerregisters every[pipelines.<name>]as<dir>/<name>, where the directory isORCA_PIPELINES_DIRorpipelines/next to the file. A missing binary is an error, and nothing is registered. A project's image setsORCA_CONFIG,ORCA_PIPELINES_DIR,ORCA_COMMITandORCA_SOURCE, soorca registeralone registers the whole file. orca aws registertakes the same--configand--no-config, checks the file locally, and sends it whole to the control instance with its git commit.- What's recorded:
orca.pipelines.configkeeps the file's name, SHA-256, git commit and whether it had uncommitted changes, the flags that overrode it, and the source of every value.
orca config show, check and export
$ orca config show gharchive
gharchive registered from orca.toml @ 3f2a9c1d0b7e, sha256 b0e8af31ea3e
overrides: --workers 0..8
placement ec2 orca.toml [defaults]
worker_type c7g.xlarge orca.toml [pipelines.gharchive]
workers 0..8 flag --workers
keys_per_run 1 code default (the pipeline's keys_per_run)
env.DISTA_WORKER_SLOTS 4 orca.toml [pipelines.gharchive] orca config show [pipeline]prints each registered value with its source:code default,orca.toml,flagorenvironment, and a learned request after the settings.--jsonprints one object, or{"pipelines": [...]}without a name. The pipeline page's Configuration card andconfigin/api/pipelines/<name>show the same.orca config check [file] [--secrets]refuses whatorca registerwould, with no database, for a project's CI. It warns about an instance type missing from morca's price table, and with--secretsreads each secret without printing it. It exits 1 on any problem.orca config export [--out -] [--force]writes anorca.tomlwith a table per registered pipeline, holding what is registered beyond the defaults, so registering from it gives the same registration back. It writes no[defaults]. Use it to move from register flags to a file.