inventory --emit-draft: a reviewable blueprint of a live instance (and an honest list of what it could not capture) #27
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#27
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The idea
cast inventorycan already see a whole instance (#22). Let it emit what itsees as a draft of cast's own inputs:
Two uses, and they are different:
the box being drained have never been declared and never will be unless
something writes the first draft. Hand-transcribing them from a UI is exactly
the work cast exists to eliminate.
rebuild an instance from. From the draft you raise adjustment PRs against the
respective repos, and the repos stay the source of truth.
The boundary (non-negotiable)
A draft is a PROPOSAL. It is never desired state, and
applynever reads it.It is emitted, reviewed by a human, and lands in a repo as a PR. Same shape
as
terraform import→ HCL. The path is:sweep → emit draft → human reviews → manifest PR →
capture→applyAnd it must never emit into a repo that already has a manifest. For a
declared project the manifest is the truth; regenerating it from a live box
would let that box's accumulated cruft overwrite the reviewed spec. Adoption is
one-way.
Two things that would make the draft actively dangerous
1. Copied provider-generated values. A
DATABASE_URLread off the sourcepoints at the source box's Postgres. Emit that into a draft, rebuild elsewhere,
and the new box comes up working — reading and writing the old box's
database. You find out the day the old box is deleted. Same for
REDIS_URL, andfor Coolify's own magic vars (
SERVICE_FQDN_*,SERVICE_URL_*,SERVICE_PASSWORD_*), which Coolify generates per-instance and which meannothing anywhere else.
So the draft applies
capture's discipline: provider-generated names areplaceheld and flagged for disposition, never copied. A draft that is
confidently wrong in four entries out of seventeen is worse than one that is
obviously incomplete.
2. Silent losses. cast cannot currently express everything a Coolify holds:
domainson services)A blueprint that omits these without saying so is worse than no blueprint,
because in a disaster you would trust it and rebuild a different box. Hence
UNCAPTURED.md: a first-class output listing, per resource, every live settingcast saw and could not express. Emitting it is not optional.
What a blueprint still cannot restore
Worth writing into the docs at the same time, because "rebuild from the repo" is
routinely over-claimed:
rig coolify install✅apply✅The last two are not in the repo (correctly — it holds no live credentials) and
cannot be regenerated from it. A DR runbook has to say so.
Depends on
The
projects:registry (#25) — a whole-instance draft needs somewhere to writethe project list it discovered.
#28 adds a hard limit to what a draft can honestly emit — and it lands squarely in
UNCAPTURED.md.A destination cannot be round-tripped. Coolify 4.1.2 takes
destination_uuidon write and returnsdestination_id(an integer PK) on read, with no endpoint mapping one to the other, and no destinations API at all.So a draft emitted from a live instance cannot produce a working
destination_uuid. It can see thatcoresits on destination5; it cannot learn the UUID that5corresponds to, because nothing exposes it. That is not a rough edge in the emitter — it is a hole in the blueprint, and exactly the kind this issue says must never be silent:Concretely, the draft must:
projects.<slug>.destination_uuid:as aTODOwith the observeddestination_idbeside it — never a guess, never omitted;UNCAPTURED.mdthat placement was observed but not resolvable, and that the UUIDs must be read out of the source instance's UI before the draft can be applied anywhere;smoke_target(now project-scoped, per #28).And the consequence for the DR story, which is the reason this issue exists: a rebuild from a blueprint alone will place every resource on the target server's default network unless a human supplies the UUIDs by hand. On a single-destination server that is silently fine. On a multi-destination server it is a hard 400 — Coolify refuses a create that omits
destination_uuidthere. Either way the blueprint did not rebuild the box it described, and onlyUNCAPTURED.mdwould have said so.Worth adding to the table in this issue's body: destinations join the GitHub App private key and the S3 access keys as things a blueprint cannot restore. All three are re-supplied by hand.