Configuration reference

A repository’s environments live in one file, replicator.config.ts, which also declares the secrets it reads. This page lists every option of it. Environments is the guide with the reasoning; Examples has complete files; The command line has the verbs that run them.

import { replicator } from "@clankernet/replicator";

export default replicator.app({
  name: "acme",
  stages: { staging: {}, production: {} },
  run(ctx) {
    return { stage: ctx.stage };
  },
});

The file imports the framework and nothing underneath it; the engine reads it with its own copy of the framework, so the repository installs nothing.

replicator.app({ … })

OptionTypeRequiredMeaning
namestringyesLowercase letters, digits and dashes, at most 40, starting with a letter. The state is kept under it: never change it after the first deploy.
stages{ [stage]: StageOptions }yesThe environments, at least one. A stage name follows the same rule as name.
run(ctx) => outputsyesBuilds one stage’s resources and returns its outputs (names and addresses, never a secret). May be async.
enginestringnoThe engine versions this file was written for (>=1.25.0 <2.0.0). An engine outside the range refuses the file. A repository without one gets a doctor warning.
state{ accountId?: string }noThe account that holds the state and the stored secrets, 32 hex characters. It must equal the account the runner’s token belongs to.
previewPreviewOptionsnoOne environment per pull request. Absent: no previews.
plainstring[]noStored values that are settings, not credentials (a zone id, an account id). The output check skips these names.
dependsOnOutputRef[]noThe other apps’ outputs this one reads with ctx.output.

Stages

stages maps a stage name to its options:

OptionTypeDefaultMeaning
protectbooleantrue for production, else falseKeep this stage’s data (buckets, databases, sites) when the program drops it. A protected stage cannot be removed without --allow-destroy '*'.

Previews

OptionTypeDefaultMeaning
patternstring—The stage name, with {number} for the pull request number, exactly once, after literal text: pr-{number}.
ttlstring7dHow long a preview is meant to live: hours or days (12h, 7d), at most 30d.

A preview is never protected and holds nothing that outlives it. The pattern may not be named like production or a protected stage, and no listed stage may match it. Each refusal names its rule: preview-pattern, preview-ttl, preview-protected or preview-collision.

dependsOn

Each entry names one plain output of another app, so the engine can read it before the stage runs:

OptionTypeRequiredMeaning
appstringyesThe producing app’s name. It cannot be this app.
stagestringyesThe producing app’s stage.
keystringyesThe output’s name: letters, digits and underscores, starting with a letter.
accountIdstringnoThe producer’s state account. The only legal value is this app’s own; another account is another trust domain and is refused.

The context: ctx

run(ctx) receives:

MemberMeaning
ctx.appThe app’s name.
ctx.stageThe stage being built (staging, production, a preview’s name).
ctx.protectWhether this stage’s data is protected. Never true for a preview.
ctx.previewWhether this stage is a preview.
ctx.secret(name)A secret stored for this stage (or for every stage); fails naming the missing one.
ctx.optionalSecret(name)The same, or undefined.
ctx.output(app, stage, key)A plain output another app published. The edge must be in dependsOn.

run may return the reserved key __env, a flat record of environment names to strings: it is written to the file infra deploy --env-out names and is never an output.

replicator.hostnames(zone, stage, rules?)

Returns a function host(sub?) that gives each stage its names inside one zone.

Argument / optionTypeDefaultMeaning
zonestring—A DNS zone (acme.example).
stagestring—The stage to name.
rules.productionstringproductionThe stage that owns the apex and plain subdomains (api.acme.example).
rules.labels{ [stage]: string }the stageThe label another stage adds, flat-dashed: api-next.acme.example, never a second level.

host() is the apex or the stage’s own name; host("api") adds one DNS label.

Presets

Plain functions you call at the top of run(ctx); each applies to every resource declared after it.

PresetWhat it does
presets.labels({ key: "value" })Labels every server, firewall and login key. A label a resource sets itself wins.
presets.stageProtection(ctx)Protects buckets and sites on a protected stage and on no other, whatever a component’s default says.
presets.site(ctx, conventions, name, args)A static site on the zone’s conventions: the production branch, a tagged record per domain, the stage’s protection. conventions is { accountId, zoneId, productionBranch? } (default main); args is { project, domains }.

replicator.transform(type, edit) is the primitive underneath: type is a provider’s type token or a regular expression, and edit({ type, name, props, opts }) returns the props or opts to use instead, or nothing. A rule lives for one stage’s run only.

Components

Every component takes a logical name first: new replicator.Bucket("uploads", { … }). The logical name keys the resource in the stage’s state, so renaming it replaces the resource.

StaticSite

OptionTypeDefaultMeaning
accountIdstring—The account that holds the site.
projectstring—The hosting project’s name.
productionBranchstring—The branch whose deploys are production.
domainsstring[]noneCustom domains to attach.
dns{ zoneId, app, stage }noneWhen set, each domain also gets a tagged record in that zone.
protectbooleantrueKeep the project when the stack drops it.

Output: url. The site’s content is published from the config’s static block, not by this component.

Worker

OptionTypeDefaultMeaning
accountIdstring—The account that holds the Worker.
scriptNamestring—The Worker’s name on the account. Never change it after the first deploy.
modulestring—The finished ES module, uploaded as it is. A relative path is read from the repository root (the folder that holds .replicator/). It must exist when the command runs.
compatibilityDatestring—The runtime date the script was written for (2025-01-01).
compatibilityFlagsstring[]noneRuntime flags (nodejs_compat).
bindingssee belownoneWhat the script can reach.
durableObjects{ migrations, adoptedTag? }noneThe history of the Worker’s durable-object classes, in order. adoptedTag is the tag a live script already reports.
routes{ zoneId, pattern }[]nonePath patterns on an existing zone (api.acme.example/*).
domains{ zoneId, hostname }[]noneHostnames the Worker owns outright.
protectbooleantrue with durable objectsKeep the script when the stack drops it.

bindings, each as binding name: what it points at:

BindingValueMeaning
varstextPlain text variables.
secretstext, usually ctx.secret("name")Sent as a secret, never shown in a plan, never an output.
secretsStore{ storeId, secretName }A secret that lives in a store (see SecretsStore).
r2bucket nameAn object bucket.
hyperdriveconfiguration idA pooled database connection.
kvnamespace idA key-value namespace.
servicesscript nameAnother Worker.
durableObjectsclass name, or { className, scriptName }A class this Worker exports (it must be in a migration), or another Worker’s.

A migration is { tag, newClasses?, newSqliteClasses?, deletedClasses?, renamedClasses?: { from, to }[] }. Add one to the end whenever the exported classes change; never edit or reorder the ones already there. A binding name used twice is an error. Output: scriptName.

Bucket

OptionTypeDefaultMeaning
accountIdstring—The account.
bucketstring—The bucket’s name.
locationWNAM, ENAM, WEUR, EEUR, APAC, OCWNAMLocation hint. Immutable after create.
protectbooleantrueKeep the bucket when the stack drops it.

Output: name.

DnsRecord

OptionTypeDefaultMeaning
zoneIdstring—The zone.
namestring—The record’s full name.
typeA, AAAA, CNAME, TXT—The record type.
contentstring—The record’s value.
proxiedbooleantrue except for TXTPass traffic through the edge network.
ttlnumber300 when not proxiedSeconds. A proxied record’s is automatic.
app, stagestring—Written into the record’s tag, so a sweep can tell ours from anyone else’s.
deleteBeforeReplacebooleantrueTwo records of one name cannot coexist, so a replace deletes first.

GeneratedSecret

OptionTypeDefaultMeaning
lengthnumber48Characters of the value.
alphanumericbooleantrueLetters and digits only (some consumers refuse symbols).

Member: value, a secret other components may read. It is never an output.

SecretsStore

OptionTypeDefaultMeaning
accountIdstring—The account.
secretNamestring—The secret’s name inside the store.
valuestring—The key. Stored as a secret, never an output.
storeNamestringthe component’s nameThe store’s name.
scopesstring[]["workers"]Who may read it.
commentstringnoneA note on the secret.
existingStoreIdstringnonePut the key into a store another stack owns (an account may hold only one). This component then never creates or deletes the store.
protectbooleantrueKeep the store and the key: losing the key loses what it wraps.

Outputs: storeId, secretName.

Hyperdrive

OptionTypeDefaultMeaning
accountIdstring—The account.
origin{ host, port?, database, user, password }port 5432The database to connect to. The password is stored as a secret and is never an output.
configNamestringthe component’s nameThe configuration’s name.
caching{ disabled?, maxAge?, staleWhileRevalidate? }caching offQuery caching.
originConnectionLimitnumberthe provider’s ownThe most connections opened to the database at once.
protectbooleantrueKeep the configuration.

Outputs: id, name.

Database

The Postgres component: one project with its first database and role, protected by default.

OptionTypeDefaultMeaning
apiKeystring—The database provider’s key (a stored secret).
projectstring—The project’s name.
regionstringaws-eu-central-1Immutable.
orgIdstringnoneThe organisation that owns the project, when the key spans several.
pgVersionnumber16Immutable.
databasestringappThe first database. Immutable.
rolestringappThe first role. Immutable.
minCu, maxCunumber0.25, 1Autoscaling floor and ceiling, in compute units.
suspendSecondsnumberthe plan’s ownSeconds idle before it suspends.
protectbooleantrueKeep the project.

Plain members: host, pooledHost, database, user. Secret members, never to be returned from run: password, uri.

Server

OptionTypeDefaultMeaning
sshPublicKeystring—The machine’s login key, public half only.
cloudflareRangesstring[]—The edge network’s address ranges allowed to reach ports 80 and 443. Required and never empty.
serverTypestring—The machine’s size.
locationstring—The data centre.
sshbooleanfalseAlso open port 22 to everyone.
imagestringubuntu-24.04The operating-system image.
userDatastringnoneFirst-boot script. Changing it replaces the machine.
labels{ [key]: string }noneExtra labels; managed-by is always set.
hostnamestringthe component’s nameThe machine’s name at the provider.
protectbooleantrueKeep the machine.

Output: ip.

The secrets block

secrets in replicator.app declares the stored values the environments read. replicator infra secrets check lists the required names a stage lacks; replicator infra secrets ensure stores them; infra diff and infra deploy refuse a stage that lacks one; replicator doctor checks the block against what the config reads, and ctx.secret of a name outside the block is an error. An app without a secrets block is not checked up front, and any name may be read.

export default replicator.app({
  name: "acme",
  stages: { staging: {}, production: {} },
  secrets: {
    sessionKey: { description: "Signs sessions", generate: "random-hex-32" },
    zoneId: { description: "The DNS zone's id" },
    analyticsKey: { required: false },
  },
  run(ctx) {
    /* … */
  },
});
KeyMeaning
(the name)Letters, digits and underscores, starting with a letter.
descriptionPrinted beside the name when it is missing. Never put a value here.
requiredDefault true: a stage cannot run without it. false for a value read with ctx.optionalSecret.
generateThe runner can mint the value: random-hex-16, random-hex-32, random-base64url-32 or uuid. ensure generates it and never prints it.

An unknown key is an error (a typo would otherwise be ignored), and a generated value cannot also be optional.

An older repository may declare the same list in secrets.schema.json (a JSON Schema with required, x-generate and x-optional) beside its config. It is still read when the config has no secrets block; with a block, replicator doctor says the file is ignored. Move the names into the block and delete the file.

The values file for ensure --values-file <file> holds NAME=value lines (blank lines and lines starting with # are skipped). The file must be readable by its owner only: a file that group or others can read, or a link, is refused before any of it is read. A name the block does not declare, a repeated name and an empty value are errors, reported by line number and never by value. A name the stage already holds is kept, so running ensure again never rotates a key.

The host owner’s policy

The host owner’s fleet file may carry a policy: block, checked on every infra diff, infra deploy and infra drift that is given --policy <fleet file>. Each refusal names its rule.

OptionTypeRuleMeaning
allowedRegionsstring[]regionThe only locations a server, database or bucket may be created in (compared case-insensitively).
requiredLabels{ key: value }labelLabels every labelled resource must carry; a value of "" requires only the key.
protectedKindsbucket, database, dns, site, secrets, serverprotected-kindKinds never deleted or replaced on any stage. --allow-destroy does not lift it.
maxStagesnumber, 1 to 50stage-countThe most stages one repository’s file may declare.

Without the block only the built-in rules apply: no data resource or DNS record is deleted or replaced unless named with --allow-destroy, and on a protected stage nothing is deleted at all.