InstanceSetting
kind: InstanceSetting declares instance-wide key/value settings stored in the
app_settings table. Unlike every other SPEC-2 kind, the document’s
metadata.name and metadata.slug fields are advisory only — they exist
to give the file a human-readable identifier in the manifest, but the real
keys live field-level inside spec.settings. Each entry in that map maps 1:1
to one app_settings row.
InstanceSetting is admin-scoped: applying it changes settings for the entire
Crewship instance, not for a single workspace. Only users with the ADMIN role
on at least one workspace can write; OWNER/ADMIN can read.
YAML schema
"587",
"true") — YAML’s typed scalars are intentionally not honoured because the
backend stores values as TEXT and re-typing at the manifest layer would
introduce drift.
How Plan works
For each(key, value) in spec.settings:
- Resolve
${ENV_VAR}placeholders in the value (see below). - Look up the current remote value in the snapshot Plan fetches
once via
GET /api/v1/instance/settings(the full key/value list) and indexes by key. There is no per-keyGETin the plan path — the whole map is pulled in a single call. - Compare:
- If the remote value equals the resolved manifest value, emit an Unchanged plan item.
- Otherwise (or if the key is missing remotely), emit an Update plan
item that will
PUT /api/v1/instance/settings/{key}with the resolved value.
crewship apply --replace), Plan additionally
enumerates every key present remotely but not declared in spec.settings and
emits a Delete plan item for each — except for protected keys (see below),
which surface as Unchanged with a “protected; skipped” description.
Environment variable interpolation
Values may contain${VAR_NAME} placeholders that are resolved at plan time
against the process environment (via os.LookupEnv). The grammar is strict:
Missing variables are a hard error, never silent. If you reference
${SMTP_PASSWORD} and that variable is unset in the apply process’s
environment, the apply aborts with a clear message naming the missing
variable. This prevents the common footgun of an empty password being
silently written into the database.
Tip: when scripting an apply, prefer
export SMTP_PASSWORD=...; crewship apply ... so the secret only exists
in the apply process’s environment and never lands in the parent shell’s
history.
Sensitive keys — best practices
Keys with these prefixes/suffixes are considered sensitive by the backend handler:- Prefix
smtp.password,oauth.,webhook. - Suffix
.password,.secret,.client_secret,.api_key,.token
"***" on read.
Plan treats "***" as “unknown” and always emits an Update plan item — the
server’s PUT handler is idempotent so a redundant write of the same value is
cheap.
Document.Warnings() returns a list of SensitiveValueWarning for keys that
match the sensitive shape but carry a literal (non-${ENV}) value. The
CLI surfaces these so you can rewrap them:
Protected keys
ApplyReplace mode is destructive — it deletes every remote key that isn’t declared in the manifest. To prevent the apply from bricking the instance, Plan never emits a Delete for these system-managed keys:
If you declare any of these in
spec.settings, the value you specify is
written normally — the protection only kicks in for the ApplyReplace prune
pass. The backend handler enforces the same whitelist as the ultimate
gatekeeper; the manifest mirror exists so dry-run output shows clean
“skipped (protected)” lines instead of opaque 403s during apply.
Endpoint contract
The manifest layer gracefully handles
404 Not Found on the list endpoint by
treating remote state as empty — this lets crewship apply --dry-run produce
useful plans even on instances where the handler is not yet deployed.
Export
crewship export workspace calls ExportInstanceSettings, which produces a
single InstanceSetting document with every server-side key collapsed into
one spec.settings map. Sensitive values appear as "***" placeholders —
this is by design: dropping them would round-trip wrong (a re-apply in
ApplyReplace mode would emit a delete for the omitted key).
After export, hand-edit the file to replace each "***" with the
appropriate ${ENV_VAR} reference before checking it into version control.
CLI surface (related)
Worked examples
Minimal — upsert one key
Mix of literal and env-interpolated values
ApplyReplace — declarative full state
- Update the four
smtp.*keys to the declared values. - Delete every other non-protected key in
app_settings. - Skip
instance.bootstrap_at,instance.first_user_id,schema.version(protected) with an “Unchanged (protected)” line in the plan.