Skip to content

The target's descriptor — /etc/cairn/adopt.toml

Where the manifest says what image to build, the descriptor says what this box runs: which image and tag to watch, which frappe_docker overrides compose the stack, the site name, and a reference to how secrets are wired. cairn-adopt reconcile reads it to converge the running stack; its mere presence on a host is what marks that host as a target at all.

It is host state, not deployment state — it describes this machine, not the deployment — so it is never committed with the manifest, and reconcile never searches for it: the path is fixed, because reconcile runs unattended under a timer with nobody present to pass a flag.

You don't hand-write this file

Run cairn-adopt examine against the frappe_docker stack already running on the host. It surveys the live deployment — compose project, layered overrides, site, the image and tag currently deployed — and prints a descriptor; it writes nothing. You review the output and install it yourself:

sudo cairn-adopt examine --environment production
# Generated by `cairn-adopt examine` from the deployment running on this host.
# Review it, then install it as /etc/cairn/adopt.toml.

environment   = "production"
registry_host = "ghcr.io"
image         = "acmecorp/erpnext-v16"
tag           = "production"
site          = "erp.acmecorp.com"

[compose]
directory = "/opt/frappe_docker"
# Read from the running project, not assumed — a hand-built deployment may not call it
# "compose.yaml".
file      = "compose.yaml"
project   = "acmecorp"
# Layered in this order — compose applies later files over earlier ones.
overrides = ["mariadb", "redis", "https"]

[health]
# timeout_seconds and interval_seconds default if omitted; url is deliberately
# left unset unless a real health-check endpoint exists.

Anything examine cannot determine is reported, with the reason, rather than filled with a guess — a transcription error here is not a typo, it's a wrong deploy. What it prints is round-tripped through cairn's own loader before being offered, so it can never hand you something reconcile would go on to reject.

Fields

Key Required Meaning
environment yes This host's environment name — joined to the manifest side only by the registry tag both agree on, never by this string directly. A label only: reconcile runs unattended under a timer with no interactive gate of any kind, so naming a target "production" has no functional effect here — the :production confirmation gate lives entirely on the build side, at the pointer move (cairn-build assign-tag/retire), not at convergence.
registry_host yes The registry this image lives in — kept separate from image rather than one string glued together, mirroring the manifest's own [cairn.registry] host. Required and explicit, including for Docker Hub: write "docker.io", the same canonical name docker itself normalizes a hostless reference to (registry.py's _DOCKER_HUB_NAMES) — there's no case where this value needs to be invented, only named. examine fills it in from the running container either way, "docker.io" included when the reference it finds has no host of its own.
image yes The image repository this host watches, without the registry host (that's registry_host, above). registry_host/image together are what reconcile actually pulls (repository), and repository:tag is the full watched reference.
tag yes The tag this host watches — the desired-state pointer.
site yes The one site this environment runs. Each environment is exactly one site; multi-site hosts are rejected — adopting one would silently drop every site but the first from the proxy configuration.

[compose]

Key Required Meaning
overrides no Ordered list of frappe_docker override names (mariadb, redis, https, …) — later entries layer over earlier ones, same order docker compose -f would take them.
directory no Where the frappe_docker compose tree lives on this host.
file no The base compose file's name within directory. Defaults to compose.yamlfrappe_docker's own convention — but examine always reads the real name off the running project and fills it in explicitly, since a hand-built deployment may call it anything.
project no Compose project name, so multiple stacks on one host can't collide.
env_file no An extra env file passed to compose, if you use one.

[health]

Key Required Meaning
timeout_seconds no Ceiling on convergence before a deploy is declared failed. Defaults to 600.
interval_seconds no Seconds between health probes. Defaults to 5; must not exceed timeout_seconds.
url no A URL reconcile fetches to confirm the site actually answers. Absent means container-health checks only — deliberately left unset by examine, since guessing a health endpoint wrong is worse than not checking one.

[secrets]

Key Required Meaning
mechanism no How DB/app secrets are wired: "docker-secrets" (recommended), "env-file", or "none" (default). Names the mechanism only — cairn never reads, stores, or sees a secret's actual value.

Any key outside this set fails to parse, naming the bad key, rather than being silently ignored.