Skip to content

Build parameters

Every input is a declared build parameter. No resource file reads process.env, so the same parameters produce the same resources, and src/params.ts is the one place any of them is read.

Terminal window
just params="--param postgres=cnpg --param size=large" up
npx chant build src --param target=kubernetes --param ingressClassName=nginx

just up is a chain of targets and just does not thread arguments through dependencies, which is why the params variable exists rather than trailing flags.

ParameterValuesDefaultNote
envany stringdevThe app.kubernetes.io/instance label on every resource. Also reads FOUNTAIN_ENV — see below
namespaceany stringfountain
imagean image referenceghcr.io/binarybourbon/fountain:v0.7.0Pinned, not a floating tag
hostauthority, port allowedlocalhost:4000PUBLIC_URL is scheme://host. Any port is stripped for PHX_HOST, the Ingress rule and certificate SANs
schemehttp · httpshttphttps turns on fountain’s redirect, HSTS and secure cookies, so whatever terminates TLS must set X-Forwarded-Proto
emailDeliverynone · resend · smtpnonefountain does not boot without a mail decision; none is a decision with two edges — see below
registrationEnabledtrue · falsetrueSets REGISTRATION_ENABLED
firstUserAdmintrue · falsetrueSets FIRST_USER_ADMIN (fountain ADR 0011): the first verified account on an instance with no admin is promoted, audit-recorded. Ignored by images ≤ v0.4.0. false keeps the manual path
databaseSsltrue · falsederived from the postgres seamfalse for bundled, true otherwise — see below
otelTracesnone · otlpnoneotlp hands export back to the standard OTEL_EXPORTER_OTLP_* variables, which this repo does not model

target is where the substrate runs, tier is how durable it is, size is how much of the machine it asks for. Separate questions, and not a free grid: Targets and tiers has which combinations are refused and why.

ParameterValuesDefaultNote
targetk3d · kubernetesk3dPicks the seam defaults coherent on that substrate and nothing else. Also reads FOUNTAIN_TARGET
tierlight · halightReplica count, Erlang clustering and backup retention. Also reads FOUNTAIN_TIER
replicasinteger ≥ 1the tier’s — 1 at light, 2 at haAbove one replica at light is a build error: the pods must form a cluster or conversation streaming breaks
sizesmall · medium · largesmall at light, large at haCPU and memory requests and limits. Changes no shape and refuses no combination

Seams start from the target’s defaults; setting one replaces exactly that seam and leaves the rest alone. The modes themselves, and the reasoning behind each refusal, are on Seams.

ParameterValuesk3dkubernetesRefuses
postgresreference · bundled · cnpgbundledreferencebundled at tier=ha
secretsreference · sops · infisicalreferencereference
ingressomit · ingress · traefikomitingressingress without ingressClassName
tlsomit · cert-manageromitomitcert-manager with ingress=omit
backupsomit · pg-dump · barman-pitrpg-dumpomitbarman-pitr without postgres=cnpg; omit with storage=floci
monitoringomit · prometheus-operatoromitomit
dataPlanesprites · spritzerspritzerspritesspritzer at tier=ha
storages3 · flociflocis3floci at tier=ha; floci with backups=omit

Which of those refusals fire on the default seams of each target is asserted in test/tiers-and-seams.test.ts, so a seam change that shifts the answer fails a build rather than making this table quietly wrong.

monitoring=prometheus-operator, ingress=traefik and secrets=infisical build and a real API server accepts the output, but no controller has reconciled any of them. Status is the authority on what has actually run.

ParameterValuesDefaultNote
secretNameany stringfountain-secretsThe Secret the app reads its environment from, whatever produced it
pgStorageSizea quantity10GiThe volume for postgres=bundled and postgres=cnpg
pgImagean image referencepostgres:16The bundled Postgres, and the client the app’s readiness wait uses
pgStorageClassany stringunsetUnset means the cluster’s default StorageClass, which is right on k3d. postgres=cnpg
cnpgImagean image referenceghcr.io/cloudnative-pg/postgresql:16.4postgres=cnpg
clusterIssuerany stringletsencrypt-productionThe ClusterIssuer the Certificate references. tls=cert-manager
ingressClassNameany stringunsetRequired when ingress=ingress; an Ingress with no class is claimed by no controller and applies cleanly anyway
traefikMiddlewareNamespaceany stringthe deployment’s namespaceWhere the HTTPS-redirect middleware lives. ingress=traefik
backupSchedulefive-field cron17 3 * * *The pg_dump CronJob. backups=pg-dump
pitrSchedulesix-field cron0 47 2 * * *The CNPG base backup. A five-field value is refused — see below
backupRetentionDaysintegerthe tier’s — 7 at light, 30 at haRetention is durability, so it comes from the tier rather than the seam
backupBucketany stringfountain-backups
backupSecretNameany stringfountain-backup-s3-credentialsThe S3 credentials the backup path reads
backupS3Endpointa URLfloci’s in-cluster Service when storage=floci, otherwise unsetUnset means the AWS default endpoint — see below
flociImagean image referencefloci/floci:1.5.34Pinned. storage=floci
spritzerImagean image referenceghcr.io/intentius/spritzer:0.5.0Pinned, because the emulator decides what a local conversation does. dataPlane=spritzer

All seven apply only at secrets=infisical.

ParameterValuesDefaultNote
infisicalHostApia URLhttp://infisical.infisical.svc.cluster.local:8080
infisicalProjectSlugany stringemptyNothing refuses an empty value
infisicalIdentityIdany stringemptyNothing refuses an empty value
infisicalEnvSlugany stringprod
infisicalSecretsPathany string/
infisicalServiceAccountany stringfountain-infisicalThe ServiceAccount the operator authenticates as
infisicalResyncSecondsinteger60

emailDelivery=none is a decision with two edges

Section titled “emailDelivery=none is a decision with two edges”

Under none the app self-verifies accounts at registration (fountain ADR 0011): a verification link that can never be delivered gates nothing, so the app stopped pretending otherwise. The other edge is that password-reset mail cannot be delivered either — a forgotten password is not recoverable in-app. For an instance whose accounts matter, configure real mail (emailDelivery=resend|smtp) or prefer OAuth sign-in.

Unset, it follows the postgres seam: false for bundled, which serves no TLS, and true for everything else. That default is right for a managed cloud database and wrong for a Postgres somebody else operates in your cluster, which is exactly the case postgres=reference exists for. Against one of those the app crashloops with (Postgrex.Error) ssl not available, so --param databaseSsl=false is how you say otherwise. Targets and tiers has the longer version.

pitrSchedule and backupSchedule are different dialects

Section titled “pitrSchedule and backupSchedule are different dialects”

CNPG cron is six fields, leading with seconds. A Kubernetes CronJob takes five. Both are accepted by the cluster and mean different times, so the two schedules are separate parameters and pitrSchedule is checked for its field count at build time. 47 2 * * * reads as 02:47 to every other cron and as second 47 of every minute 2 to CNPG — 24 base backups a day, with no error anywhere. The check and its message are on Seams.

backupS3Endpoint resolves from inside a pod

Section titled “backupS3Endpoint resolves from inside a pod”

The only thing that reads it is the upload container, so the endpoint has to resolve from the cluster. At storage=floci it defaults to floci’s Service name, which does. A laptop-style http://localhost:4566 leaves the container talking to itself: inside a pod, localhost is the pod. Unset at storage=s3 means the AWS default endpoint, which is what a real bucket wants; point it at R2 or Garage to use one of those instead.

env labels the resources. chant.config.ts reads FOUNTAIN_ENV from the process for ownership.env, because ownership is resolved when the config loads and build parameters do not exist yet at that point. So --param env=prod labels resources prod while the ownership marker still says dev unless FOUNTAIN_ENV says prod too. Set both or neither. INTENTIUS/chant#1396 tracks it. The justfile exports FOUNTAIN_ENV=local, so a local deploy is labelled, owned and bound under one name.

target and tier take the same treatment through FOUNTAIN_TARGET and FOUNTAIN_TIER, but those are ordinary parameter env mappings with nothing reading them behind the build’s back.

none is the default and the emitted value. fountain v0.4.0 exports traces only when an export target is configured, so this is an explicit statement of what would happen anyway — kept because “off” being visible in the pod spec is worth one line, and because the parameter is how an operator turns export on.

Setting otlp hands the decision back to OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS, which are yours to supply; nothing here models them.