fix(apply): create databases and services before the applications that need them (#45) #53
No reviewers
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#53
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/create-order"
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 problem
applyPlanwalksreport.changesin the order the report was built, and that order is the manifest's:desiredFromManifest(resolve.ts) pushes applications (~347) → databases (~373) → services (~385), andcomputeDiffpreserves it faithfully.So a first apply creates the compose app and deploys it (
createResource→syncEnv→redeploy, all inside one loop iteration) before the Postgres and Redis it talks to exist at all. A full build, a full deploy, and a red deployment in the UI — guaranteed, on every first apply. A tool whose first run always looks broken teaches people to ignore its output.What changed
src/apply.tsonly. Apply now acts in dependency order —database→service→application— instead of manifest order.KIND_ORDERis exported as the forward kind-order. It cannot be a computed graph, and the issue is right about why: nothing in a manifest declares thatcoreneedspostgres— no resource names another, anywhere — so the dependency edges do not exist to be walked. The direction between kinds is not in question, and three kinds is few enough to legislate.Record<ResourceKind, number>rather than a list +indexOf, so a fourthResourceKindfails the build until someone decides where it goes. A list would rank an unranked kind-1— i.e. ahead of databases — silently reintroducing this exact bug for the new kind.KIND_ORDERis derived from the ranks so the two cannot drift.report.changesis whatrenderDiffprints and what a fleet run reports on, and that reading order is the manifest's, deliberately. The sort is stable (ES2019), so within a kind the manifest's order survives.clean/orphans/placementand the two refusals are untouched —src/diff.tsis not modified.Where the issue was slightly optimistic, and the honest limit
The issue reasons as though existence is the whole problem ("the database ... was not going to exist for another few seconds' worth of API calls"). Existence is the part cast can fix, and this fixes it — but ordering the API calls is not a readiness barrier, and the PR says so rather than implying a stronger guarantee.
From
DeployController@deploy_resource(coollabsio/coolify v4.1.2, the endpointredeployhits viaclient.deploy):queue_application_deployment(application: $resource, deployment_uuid: ..., is_api: true)— queued.StartDatabase::dispatch($resource)— queued ("Database starting request queued.").StartService::run($resource)— the only one that runs synchronously.So cast orders its requests; Coolify runs them on its own queues. An app whose first boot must find a listening database still races it. What this PR removes is the guaranteed failure — deploying against databases that do not exist at all — not the race. A readiness barrier (poll
GET /databases/{uuid}until running, before the app deploy) would be a separate change, and a bigger one.Also confirmed while reading the source, and not acted on because
cli.tsis out of scope for this PR:POST /databases/postgresqldoes acceptinstant_deploy(DatabasesController$allowedFields, line 1646; documented at 1134+), and cast does not send it — databases are started by the separateredeploystep instead. That works, and changing it is not this PR's business, but it is a simplification available to whoever ownscli.tsnext.Tests
test/apply.test.ts, 5 added (294 → 299, all green undernpm run check && npm run build && npm test). Edge paths, not just the happy one:create postgres → create redis → create metabase → create core, withcorecreated and deployed last; asserts the report itself still reads in manifest order, and that within a kind manifest order survives (postgres before redis — stable sort).coreat it creates + starts the database before the app's env sync and redeploy.build_packdrift on the application refuses the run even though a creatable database now sorts ahead of it. This is the regression the reorder could have introduced (a check folded into the ordered walk would have created postgres first), so it is locked in.applyPlandoes not sortreport.changesin place —renderDiffand the fleet summary keep the operator's reading order.KIND_ORDERis["database", "service", "application"], and its reverse is the teardown order.Coordination
#43(cast destroy) needs the exact reverse ofKIND_ORDERand defines its own teardown constant in its own new file, per the parallel-PR split. Follow-up: unify the two into one pair of constants once both have landed — the symmetry is the point (things come up in the order their dependencies allow and go down in the reverse), and it should live in one place.src/diff.tsdeliberately untouched (owned by other in-flight PRs); the order lives entirely at apply time.Operator acts
None. No config, no migration — the next
cast applysimply acts in the right order.docs/semantics.mdgains a bullet under Apply semantics stating the order, why it is a constant and not a graph, and the queue-vs-readiness limit above.Closes #45.
🤖 Generated with Claude Code