cast cannot place a resource on a destination — and the state file has nowhere to say which #21
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#21
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 gap
cast cannot place a resource on a chosen destination, and the state file has
nowhere to say which one it should be on.
applycreates every application withproject_uuid,environment_nameandserver_uuid— and nodestination_uuid(buildExecutor,src/cli.ts). Coolifytherefore puts everything on the server's default destination: one Docker
network, shared by every project on that server.
This is invisible and harmless today, because each of our servers hosts exactly
one project. It stops being either the moment a server hosts two.
The trap it sets right now
An operator who defines destinations by hand in the Coolify UI — the natural
thing to do when you want two projects isolated — will find that
applyignoresthem. Resources land on the server's default network anyway, nothing reports a
mismatch, and
diffdoesn't compare the field because the manifest cannotexpress it. The isolation looks configured and isn't.
That is the failure shape cast keeps legislating against (#12, #14, #17, #18): a
setting that reads back as absent rather than wrong.
The motivating case
A live migration (D-193). The box being drained hosts three projects — the one
being migrated, plus two unrelated third-party client sites. Today they share the
old box's default network. When they are eventually consolidated onto a single new
box, they must not share one: a per-project destination per environment is
exactly the mechanism, and neither cast nor the state repo can currently say so.
Note what is NOT needed: per-environment isolation on its own. Prod and staging
already land on different servers, and a server's default destination is its
own network — so that isolation comes for free from the server split. The
requirement is specifically project isolation on a shared server.
The state-shape problem it forces
A destination is scoped project × environment.
environments.yamlis scopedby environment alone:
Adding
destination:there would mean one destination per environment, shared byevery project in it — which is precisely the isolation this is meant to provide,
inverted.
So this issue is the forcing function for a structure change that is already
overdue.
smoke_target: corehas the same defect (it names incubator's composeapp, under a key that claims to be about the environment), and
github_appshasalready been fixed the right way — keyed by repo. The destination belongs in
the same place:
...or the manifest declares its own destination name and cast resolves it per
environment. Both are defensible; the current shape is not, once there are two
projects.
Proposal
destination_uuidoncreate, sourced from state (or the manifest) rather than defaulted.
overloading the per-environment block.
github_apps.<repo>is the precedent.smoke_targetthere in the same pass — it is project-scoped state livingin an environment-scoped key today, and it will be simply wrong the day a
second project deploys into
prod.diffshould compare the destination once it can be declared, so a resourcequietly sitting on the wrong network is drift rather than silence.
Not urgent
Deliberately not on the prod-migration critical path. One project per server
today ⇒ the default destination is correct ⇒ this changes nothing about the
migration. It is needed before two projects share a box, which is a separate
piece of work on its own clock. Filed now so that it is a decision rather than a
discovery.
Verified against the vendored
reference/coolify-openapi-4.1.2.json— posting before this gets rediscovered, since a parallel session is on this issue.Destinations cannot be CREATED via the API
There are zero
/destinationspaths and noDestinationschema in 4.1.2. A destination is a UI act, full stop.That is survivable and has precedent: the S3 storage destination and the GitHub App are both created by hand and then recorded into
environments.yamlby UUID, precisely because 4.1.2 has no API to resolve them by name. Destinations should follow the same pattern — create by hand, record the UUID in state, let cast use it.But resources CAN be placed on one
destination_uuidis accepted on:POST /applications/{dockerfile,dockerimage,public,private-deploy-key,private-github-app}POST /databases/{postgresql,redis,mysql,mariadb,mongodb,keydb,dragonfly,clickhouse}POST /servicesPATCH /applications/{uuid}·PATCH /services/{uuid}So placement is fully automatable. cast just cannot mint the network.
The trap: placement is barely verifiable
This is the part worth designing around rather than discovering:
destination_uuid— a string uuiddestination_type+destination_id— an integerStandalonePostgresqlhas no destination fieldTwo consequences:
diffcannot detect destination drift. You write a uuid and read back an int, and with no list endpoint there is no way to build the uuid↔id mapping. Either state records both (uuid to write, id to compare — ugly but honest), or destination is excluded from the diff and treated as create-only, the way servicedomainsalready is.connect_to_docker_network, which cast already emits on create only.Net: destination is declare-once, place-on-create, mostly unverifiable. Getting it wrong is corrected by recreating the resource, not by re-applying — so the create path is the one that has to be right, and a silent default (today: the server's default network) is the failure mode.
Not urgent for the migration in flight
One project per server ⇒ the default destination is correct ⇒ nothing about the current prod migration changes. This bites when two projects share a box.