Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

project.cfg schema

project.cfg is the per-project config, read by the client commands (sync, build, bundle, validate). It is RON, lives in the project folder, and is optional — a missing file means all defaults.

(
    publish: ( server: "https://pad.example.com", site: "my-site" ),
    build: ( command: "npm run build", output: "dist" ),
    routing: (
        clean_urls: true,
        redirects: [ (from: "/old/:slug", to: "/new/:slug", status: 301) ],
    ),
)

Sections:

SectionPurpose
publishWhere and what to publish (sync).
buildAn optional build command run before sync.
bundleThe in-process JS/CSS bundler (bundler feature).
routingRedirects, rewrites, headers, handlers — folded into the deployment.

publish

FieldTypeDescription
serverurlServer base URL. Flag --server, env BOATRAMP_SERVER.
sitestringSite to publish to. Flag --site, env BOATRAMP_SITE.
tokenstringControl-plane token. Prefer BOATRAMP_TOKEN so it is not on disk.
projectstringThe project this config’s site belongs to; overridden by --project / BOATRAMP_PROJECT, defaults to default.

See also the separate apply.cfg project manifest, which declares a whole project — its member sites, top-level functions, compute workloads, managed databases, and tenancy schema — as one applied unit. Its field-by-field schema is below.

build

Run before sync; its output directory is what gets published.

FieldTypeDescription
commandstringShell command to run (e.g. npm run build).
outputstringDirectory the build emits and sync publishes (e.g. dist).

bundle

The in-process bundler (Rolldown for JS/TS, lightningcss for CSS). Needs the bundler feature.

FieldTypeDefaultDescription
outdirstringdistOutput directory for bundled assets.
jslist—JS/TS entry points (tree-shaken, code-split).
csslist—CSS entry points (@import inlined).
minifybooltrueMinify the output.

routing

The bulk of a project’s config: redirects, rewrites, headers, SPA fallback, clean URLs, error documents, and the handler/consumer/cron/stream declarations. It is compiled and checked at sync (and by boatramp validate), then folded into the immutable deployment manifest — so it is atomic with the content and rolls back with it.

The full field-by-field schema is on its own page: Routing config schema.

Validate a project.cfg (including routing) without publishing:

boatramp validate
project.cfg: routing OK (2 redirects, 1 handler)

apply.cfg manifest schema

apply.cfg is a separate, project-level RON manifest read by boatramp apply. Where project.cfg configures one site’s publish, apply.cfg declares a whole project — its member sites, top-level functions, compute workloads, managed databases, and tenancy schema — and reconciles it as one applied unit. It is upsert, never prune: apply create-or-replaces only the resources it names and never deletes anything absent from the manifest, so declarative and imperative management coexist.

Unlike project.cfg, a missing manifest is an error (there is nothing to apply). The default filename is apply.cfg (-f overrides it).

Manifest top-level fields

FieldTypeDefaultDescription
versionu32?absent ⇒ currentManifest schema version — see below.
projectstring?resolvedTarget project. Absent ⇒ --project / BOATRAMP_PROJECT / the default project.
siteslist<ApplySite>[]Sites to publish (each an atomic content-addressed deployment; see the apply how-to).
functionslist<ApplyFunction>[]Top-level functions to deploy (create-or-replace).
computelist<ApplyCompute>[]Compute workloads to create-or-replace — see compute.
databaseslist<ApplyDatabase>[]Declared managed databases — see databases.
tenancyTenancySchema?untouchedThe project’s tenant-isolation schema. Reconciled before sites/functions. Absent ⇒ the stored schema is left untouched (use boatramp tenancy clear to remove one).

The whole document is parsed with deny_unknown_fields, so a typo or an excluded key fails to parse rather than being silently ignored.

version + migration

The optional top-level version: <u32> opts a document into the migration framework (v0.6.0):

  • Absent (or equal to the current schema) ⇒ parsed strictly against the current typed schema. An old-shaped manifest that omits version fails with an upgrade error naming the migration path (the most common cause is a pre-v0.6.0 raw-JSON compute[].spec).
  • version: N older than current ⇒ the document is run through the registered migration chain (vN → … → current) via boatramp config migrate <file> (--write rewrites in place), then parsed strictly. An upgraded/migrated manifest omits version: (current = absent).
  • version: N newer than this build understands ⇒ rejected.

Declare the schema you wrote against to get migration support; omit version: and your manifest is parsed as current. Add version: 1 (the pre-v0.6.0 schema) only when upgrading an old manifest with config migrate.

The manifest is authored in RON. As of v0.6.5 a JSON manifest is also accepted for interop (current schema only — a JSON document is not run through the migration chain); RON stays the canonical authoring format. See the config formats how-to.

compute workloads (ComputeSpec)

Each compute[] entry is a workload name plus a typed spec (v0.6.0 — before this, spec was a raw JSON blob; it is now the typed ComputeSpec, so a malformed spec fails at parse time). It mirrors the server’s PutComputeRequest:

FieldTypeDefaultDescription
namestring—Workload name (project-scoped).
specComputeSpec—The immutable workload spec (below).
replicasu321Desired replica count.
placementPlacementConstraintsnoneregions (list) + labels (map) a replica’s node must satisfy.

ComputeSpec key fields:

FieldTypeDefaultDescription
rootRootSource—The workload’s root filesystem source — see RootSource below.
kernelstring—Blob hash of the vmlinux kernel; applies only to a micro-VM (rootfs(…)) source, omitted otherwise.
vcpusu32—Virtual CPUs.
mem_mibu32—Guest memory (MiB).
portu16—The in-guest TCP port the app listens on (the gateway targets it).
entrypointlist<string>[]The in-guest argv the init execs.
envmap<string, string>{}Environment variables for the entrypoint.
volumeslist<VolumeRef>[]Persistent volumes (mount / name / size_mib); opt-in (default root is read-only + ephemeral scratch).
restartenumalwaysnever (run-to-completion), on_failure, or always.
startup_grace_secsu3230Window a fresh replica has to become healthy before it is treated as a broken launch.
isolationenumtrustedtrusted (shared-kernel container is fine) or untrusted (requires a micro-VM / managed platform).
scale_to_zeroboolfalseSnapshot + stop when idle; cold-restore on the next request.
writable_rootboolfalseWritable root FS instead of the hardened read-only default (honored only under the single-tenant posture).
bindingslist<ComputeBinding>[]Managed resources (kind: sql, …) resolved to a tenant-scoped endpoint + credential injected into the guest env at launch.

RootSource

A tagged, snake_case newtype variant selecting the root FS form (matched 1:1 to the backends that accept it):

VariantSourceBackends
image("repo:tag")An OCI image reference pulled from a registry.docker, cloudflare
tar("<blob-hash>")A tar rootfs archive (a shared-store blob hash) staged + unpacked.native container
rootfs("<blob-hash>")A rootfs block image (a shared-store blob hash) attached as the root device (paired with kernel).firecracker micro-VM
compute: [
    ( name: "api",
      spec: ( root: image("ghcr.io/acme/api:1"), vcpus: 1, mem_mib: 512, port: 8080 ),
      replicas: 2 ),
]

Managed databases (databases)

The databases: block (v0.6.0) is the declarative front door onto boatramp’s managed-database provisioning — the SOLE authoring surface for a project-scoped managed DB (there is deliberately no imperative db create; a create verb would compete as a second source of truth). Declaring an entry no longer requires an operator to hand-edit boatramp.cfg — a project author adds an entry and runs boatramp apply.

Each ApplyDatabase is a typed, SAFE projection of the node-static external database config, restricted to the fields a project author may safely declare. Reconciled before sites/functions/compute, so a handler shipped in the same apply binds an already-provisioned DB. It is PUT-only (create-or-replace + eager provision); removing an entry NEVER deprovisions the database, volume, or credential (teardown stays an explicit imperative verb — a data-loss guard).

FieldTypeDefaultDescription
namestring—Binding name — how a guest reaches it via sql.open("<name>") and the {name} key segment.
kindenum—The engine: postgres or mysql.
versionu32?engine defaultEngine major version (e.g. 16). A change on re-apply routes through the owner-gated migrate/repair path, never a silent re-init.
extensionslist<string>[]Trusted extensions to make available (Postgres). Advisory — enabling one still routes through the owner-gated migration step + operator allowlist.
sizeenumsmallSizing preset: small / medium / large → bounded vcpus/mem/volume (NOT raw VM knobs — the disk-exhaustion guard).
tenantenumsingleIsolation mechanism: single (dedicated server per tenant) or shared (one server, per-tenant db + role). A change on re-apply is refused.
tenant_scopeenumprojectTenant grain: project or site. A change on re-apply is refused.
read_onlyboolfalseOpen every transaction READ ONLY.
rls_sessionboolfalseOpt-in native-RLS session injection.
tenant_gucstring?—Postgres session GUC for the host-resolved tenant (RLS backstop; honored with rls_session + Postgres).
session_gucstring?—Session GUC for the anonymous session axis (RLS backstop).
tenant_all_markerstring?—The reserved sentinel written to tenant_guc on an all-scoped read.
pool_maxu32?—Max pooled connections. Capped to an operator ceiling (64) at lowering.
connect_timeout_secsu64?—Connection/acquire timeout. Capped (60s).
startup_grace_secsu32?—Startup grace for the managed server’s first initdb. Capped (600s).

Excluded — the security contract. These are not fields; a manifest that names one fails to parse (deny_unknown_fields), because the credential is minted + sealed server-side and never lives in a committable manifest:

Excluded fieldWhy
imageArbitrary-OCI RCE — boatramp always picks the stock engine image at lowering.
password_envOmitting it is what selects the managed-credential path; a declared DB can NEVER bring its own password.
url_env / read_url_env / migration_url_envBYO-secret / SSRF / arbitrary-host reach.
pathHost-fs traversal.
computeThe per-project server workload is DERIVED (project-qualified), never author-named — so a manifest can only provision onto its own project’s server.

Semantics. Declaring a database mints owner-role identities, so the declare + provision route is Project·Admin-gated (the same owner-grade placement as migrate/repair — a project publisher/deployer can never reach it). A declared database is persisted project-scoped at project/{project}/database/{name} and MERGED with the node-static [handlers].bindings.sql.databases map at one resolution point where daemon-static config WINS, fail-closed, on a same-name conflict — a project manifest may never shadow or downgrade a node operator’s bring-your-own binding. Two operator ceilings guard the node’s disk: max_declared_databases (count, default 16) and max_declared_volume_mib (aggregate volume, default 512 GiB), enforced fail-closed at declare (a 422).

Read-only inspection is boatramp db ls | get <name> | status <name> (CLI reference); there is no db create. See the apply how-to for the end-to-end flow.

databases: [
    ( name: "app", kind: postgres, version: 16, size: medium,
      tenant: shared, tenant_scope: project, extensions: ["pgcrypto"],
      rls_session: true, tenant_guc: "app.tenant_id" ),
]