Install and deploy
cairn ships in two supported shapes, and both run the same engine image:
| Single host | Cluster | |
|---|---|---|
| What you deploy | deploy/prod/docker-compose.yml | the Helm chart in deploy/helm/cairn |
| HTTPS | Caddy, automatic Let’s Encrypt | your Ingress + cert-manager |
| Database | Postgres, bundled | Postgres, bundled or external |
| Optional services | compose profiles | profiles.* values |
| Check before you start | cairn deploy doctor --env .env | cairn deploy doctor --values my.yaml |
If one VM is enough, use one VM. The compose stack is not a lesser option.
Single host with Docker Compose
Section titled “Single host with Docker Compose”What runs by default:
internet ──▶ caddy :80/:443 (automatic TLS) ──▶ server (engine + console + scheduler) └ run store, checkpoints ▶ postgresOnly Caddy is exposed. The console is served by the server at /console.
-
A small Linux VM. 2 vCPU and 4–8 GB of memory is enough to start.
-
DNS. Point an A record (for example
cairn.example.com) at the VM. -
Get the code onto the box and go to
deploy/prod. -
Configure.
cp .env.example .env, then fill in the required values:Variable What it is How to make one CADDY_DOMAINthe public domain Caddy gets a certificate for your DNS name POSTGRES_PASSWORDthe app database password openssl rand -hex 16OBS_JWT_SECRETsigns sign-in tokens; auth is on in production openssl rand -hex 32OBS_API_PASSWORD_HASHthe password of the admin user ( OBS_API_USER, defaultadmin)cairn remote hash-passwordOBS_PROFILEthe deployment profile: which operator serves each capability, plus audit and redaction settings. The engine refuses to start without one. the path to prod.tomlinside the image, as in.env.example -
Check the configuration before anything starts:
Terminal window cairn deploy doctor --env .envIt lists every required value that is missing and checks that the runtime you chose matches the compose profiles you enabled. On an untouched
.env.exampleit reports the five values above as failures. -
Deploy:
./deploy.shinstalls Docker if it’s missing, builds the image and starts the stack. -
Verify once the certificate is issued:
Terminal window curl https://cairn.example.com/healthzThen open
https://cairn.example.com/console.
The .env is the configuration
Section titled “The .env is the configuration”deploy/prod/.env is the only copy of the deployment’s configuration. Back it
up before every change (cp .env .env.bak.$(date +%s)), and never copy a local
.env over the one on the box.
Optional services
Section titled “Optional services”Add services with compose profiles: list them in COMPOSE_PROFILES in .env.
The ones most deployments consider:
| Profile | Adds | Also set |
|---|---|---|
temporal | Temporal and a worker, for durable execution across restarts | OBS_RUNTIME=temporal |
backup | a nightly pg_dump of the app database | nothing |
nats | NATS JetStream, a durable inbox for triggers | OBS_TRIGGER_DURABLE=1, OBS_TRIGGER_INBOX=nats://nats:4222 |
infisical | Infisical, to keep secrets out of the plain .env | the Infisical variables in .env.example |
otel + observability | an OpenTelemetry collector, Prometheus and Grafana | nothing |
mlflow | an MLflow tracking server and model registry | MLFLOW_TRACKING_URI |
Without a runtime profile the engine runs work in-process
(OBS_RUNTIME=inprocess, the default). cairn deploy doctor fails loudly if
you set OBS_RUNTIME=temporal without enabling the temporal profile.
Kubernetes with Helm
Section titled “Kubernetes with Helm”The chart is a translation of the compose file, not a second architecture: a test fails the build if the two set different engine variables.
kubectl create namespace cairn
# only while the engine image on GHCR is privatekubectl -n cairn create secret docker-registry ghcr-pull \ --docker-server=ghcr.io --docker-username=<gh-user> --docker-password=<read:packages token>
cp deploy/helm/cairn/values-postgres.example.yaml my-values.yaml # edit every CHANGE-ME
cairn deploy doctor --values my-values.yaml --strict
helm upgrade --install cairn deploy/helm/cairn -n cairn -f my-values.yaml--strict turns warnings into failures; it fails on an unpinned latest image
tag, so pin image.tag. Three example values files ship with the chart:
bundled Postgres (values-postgres.example.yaml), an external database
(values-external-dsn.example.yaml) and Temporal
(values-temporal.example.yaml).
Then kubectl -n cairn rollout status deploy/cairn-server and
curl -fsS https://<your host>/healthz. The console is at /console.
Sign-up and sign-in
Section titled “Sign-up and sign-in”Who can create an account, and how people sign in, are two separate settings.
Who can sign up: OBS_SIGNUP.
| Mode | Who can create an account |
|---|---|
invite (default) | only someone holding an invite link from an admin |
domain | anyone whose work-email domain an account has registered; they join that account as a member. Anyone else is refused, and a personal-mail domain (gmail.com and the like) never counts unless an admin registered it. |
open | anyone; each sign-up creates a new company account with its founder as owner |
On a deployment with no accounts yet, the first person to sign up becomes the owner of a new account, whatever the mode. That is how the first admin is created.
How people sign in. Email and password always work. To offer GitHub or Google as well:
-
Set
OBS_PUBLIC_URLto the public origin, for examplehttps://cairn.example.com. Behind Caddy this is required: without it the callback is built from the internal address and the provider rejects it. -
Register an OAuth app with the provider, with this exact callback URL:
- GitHub:
https://cairn.example.com/auth/oauth/github/callback - Google:
https://cairn.example.com/auth/oauth/google/callback
- GitHub:
-
Put its client ID and secret in
.env:Terminal window OBS_OAUTH_GITHUB_CLIENT_ID=...OBS_OAUTH_GITHUB_CLIENT_SECRET=...(
OBS_OAUTH_GOOGLE_CLIENT_IDandOBS_OAUTH_GOOGLE_CLIENT_SECRETfor Google.) -
Restart the server.
A provider’s button appears only when auth is on and its client ID is set.
Signing in with GitHub or Google does not bypass OBS_SIGNUP: an unknown email
still needs an invite in invite mode.
- Quickstart: run cairn locally with demo data.
- Set up your team: invites, the setup wizard and roles.
- Capabilities, operators and environments: what the deployment profile binds.