Audit: every manual Coolify-UI step, verified against v4.1.2 and next — two claims refuted, two gaps survive into v4.2 #72
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#72
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?
Audit: every manual Coolify-UI step cast requires, verified against v4.1.2 and
nextGoal: get cast to zero UI-tweaking. This is a sequential audit of every place cast tells the operator to do something by hand in the Coolify dashboard, each claim re-verified against the Coolify source at tag
v4.1.2(what we run) and againstupstream/next(the unreleased v4.2 train, PR coollabsio/coolify#10872).Method: enumerated every manual-UI touchpoint from
docs/semantics.md,src/draft.ts(NO_API_COVERAGE),reference/README.md, and the warn sites insrc/resolve.ts/src/cli.ts; verified each againstv4.1.2controllers/routes with file:line evidence; diffedv4.1.2..upstream/nextfor API additions; searched coollabsio/coolify issues/discussions/PRs for existing feature requests.Headline: two of cast's documented "no API for this" claims are wrong at v4.1.2 (service hostnames; GitHub App visibility/creation) — the same failure mode the #51 backup-schedule post-mortem warned about: a defect filed as a limitation does not get fixed. Three more are real in 4.1.2 but solved on
next. Only two genuinely survive into v4.2: S3 storages and backup S3-target readback.The matrix
"In 4.1.2?" = can it be done via the API in the release we run today. "In next?" = added on
upstream/next(v4.2).resolve.ts:673,semantics.md"Known limitations",draft.tsNO_API_COVERAGEresolve.ts(~L555),semantics.mdis_staticreadback #70, gzip, stripprefix, build cache, …)draft.tsNO_API_COVERAGEenvironments.yamlreference/README.md,draft.ts#21,semantics.mdPlacementreference/README.md"Known gap"s3_storage_uuid)semantics.mdBackup schedulesdraft.tsNO_API_COVERAGEsemantics.mdgithub_apps,draft.tsCANNOT_RESTOREforce_domain_overridediff.ts,apply.tsnextaddsPOST /{resource}/{uuid}/move(between environments)Findings in detail
1. Service hostnames — REFUTED: 4.1.2 can do this today
semantics.mdsays the claim was "re-checked and holds: Coolify 4.1.2 exposes no flatdomainson a service, on any route", anddraft.tssays per-containerfqdn"can neither read nor write". Both halves are false atv4.1.2:ServicesControlleracceptsurls(array of{name, url}, comma-separated URLs per container) on bothPOST /services(create allowlist + validation,ServicesController.php:296,320-324) andPATCH /services/{uuid}(allowlist:962).applyServiceUrls()(:45-135) matchesurls[].nameto aServiceApplicationand setsfqdn, with cross-team domain-conflict detection (409 unlessforce_domain_override=true). Landed in coollabsio/coolify#7929, merged 2026-01-14 — well before 4.1.2.GET /services/{uuid}does$service->load(['applications', 'databases'])(:736) andremoveSensitiveDatadoes not hidefqdn— per-container hostnames come back. Create/PATCH responses even return'domains' => $service->applications()->pluck('fqdn').GET /servicesdoesn't load applications, and there's no per-container sub-route in 4.1.2 (that arrives onnext:GET/PATCH /services/{uuid}/applications/{app_uuid}, where PATCH takesurl→ stored as fqdn).What's actually missing is on cast's side: the manifest's flat
domains: string[]has no per-container name to buildurls[]from. Compose applications already solve this shape withservice_domains(map of compose service → URLs); services need the same vocabulary. With that, the warn inresolve.ts:673, the drop inserviceApiFields(), and thedraft.tsUNCAPTURED rows all become deletable, and service domains become a diffed field — exactly the #51 arc repeating.2. Include Source Commit in Build — confirmed for 4.1.2, solved on next
Confirmed:
include_source_commit_in_buildappears in zero API controllers atv4.1.2, and both allowlists (ApplicationsController.php:914,:2368) reject unknown keys with "This field is not allowed." Only writer is the Livewire Advanced tab.On
next: first made PATCH-able by #10551 (63ba33261), then commit34e6a6dd5("feat(api): add application settings to application endpoints", 2026-07-14) adds a fullAPPLICATION_SETTING_FIELDSallowlist to PATCH and all create variants, including this flag.3. Settings-tab toggles generally — 4.1.2 partial and write-only, next solves read+write
At
v4.1.2, exactly eightApplicationSetting-backed fields are API-settable (is_static,is_spa,is_auto_deploy_enabled,is_force_https_enabled,connect_to_docker_network,use_build_server,is_container_label_escape_enabled,is_preserve_repository_enabled) — and none are readable: no read path loads thesettingsrelation (the #70 finding, reconfirmed).On
next,34e6a6dd5changes both sides:GET /applications/{uuid}eager-loadssettingsand returns the wholeApplicationSettingrow (minus ids/timestamps). This un-degradesis_staticfrom create-time-only — the exact upstream errand #70 tracks. Note #70's "upstream is identical as of 2026-07-15" was checked against the wrong branch;nexthas had this since 2026-07-14. The fork branchfeat/api-get-application-settings(dedicatedGET /applications/{uuid}/settingssub-route) is now redundant — upstream closed danmt's #10948/#10949 as duplicate of this commit.is_git_submodules_enabled,is_git_lfs_enabled,is_git_shallow_clone_enabled,disable_build_cache,inject_build_args_to_dockerfile,include_source_commit_in_build,is_env_sorting_enabled,is_pr_deployments_public_enabled,stop_grace_period,docker_images_to_keep,is_gzip_enabled,is_stripprefix_enabled,is_raw_compose_deployment_enabled) plususe_build_secrets.Maintainer confirmation in discussion coollabsio/coolify#10823: "This will be added to the next (4.2) version!"
4. Destinations — confirmed for 4.1.2, solved on next
Confirmed: zero routes matching
destinationinroutes/api.phpatv4.1.2; reads return only the integerdestination_id+ morph type; nothing maps int → UUID (reference/README.mdfindings all hold).On
next: coollabsio/coolify#10405 (merged 2026-07-02) addsGET /destinations,GET/DELETE /destinations/{uuid},GET/POST /servers/{server_uuid}/destinations. Response carriesuuid,name,network,type,server_uuid. Create takesnetwork(+ optionalname,type); duplicates rejected; races handled as conflicts. The commit message explicitly motivates it with our use case ("blocks IaC tools").Residual even on next: resource reads still return
destination_id(int), and/destinationsdoesn't expose the internal id — so verifying an existing resource's placement (int → uuid) is still impossible; you can enumerate and choose, not confirm. Small upstream ask, see feature requests.5. S3 storage destinations — confirmed, NOT solved on next
Confirmed at
v4.1.2and unchanged onnext: no list/create/read routes for S3 storages anywhere; the onlystoragesroutes are per-resource volume mounts. The only API code touchingS3Storageis backup-payload validation. Thes3_destinationUUID inenvironments.yamlstays a hand-copied value from Settings → S3 Storages.No existing upstream issue/discussion/PR asks for this. Genuinely unclaimed.
6. Backup schedule S3 target — confirmed, NOT solved on next
Confirmed:
GET /databases/{uuid}/backupsreturns raw Eloquent rows with integers3_storage_id; writes takes3_storage_uuid(validated, converted, unset). Byte-for-byte identical onnext. Combined with #5 there is no id→uuid recovery, so cast's "assert on every write, never verify" stance remains forced. (nextdoes addGET /databases/{uuid}/backups/{scheduled_backup_uuid}/executionsand ClickHouse backup support — adjacent, not this.)7. Basic Auth / custom Traefik labels — apps possible today, services not
Partially refuted at
v4.1.2for applications:custom_labels(base64-validated),is_http_basic_auth_enabled,http_basic_auth_username,http_basic_auth_passwordare in both create/PATCH allowlists; reads ofcustom_labels/password are gated behind a sensitive-data-enabled token. Caveats: enabling basic auth or changing domains regenerates labels (overwritescustom_labelsunlessis_container_label_readonly_enabled— which is itself not API-settable in 4.1.2). For services: nothing, in either release.So the UNCAPTURED.md row "cast's manifest has no field for them, so a rebuilt resource is UNPROTECTED" is a cast vocabulary gap for applications, an API gap only for services.
Upgrade heads-up: on
next, the token-abilities work (8b7dbbafb,9a2c432c7, …) putscustom_labels,http_basic_auth_password,dockerfile,docker_compose(_raw), webhook secrets behindread:sensitive/roottoken abilities at the model level. Cast's API token will need those abilities after the v4.2 upgrade or reads silently lose fields.8. GitHub App association and registration — REFUTED: mostly visible/creatable in 4.1.2
semantics.mdclaims "nothing Coolify returns about an application says which App cloned it". False atv4.1.2:removeSensitiveDatadoes not hidesource_id/source_type, so every application GET returns them, andGET /github-apps(routes exist atroutes/api.php:131-136) returns each App'sid— the association is resolvable via API. Cast's "bind to the only App or write a REVIEW marker" heuristic can be replaced with an actual lookup.Creation:
POST /github-apps+POST /security/keysexist at 4.1.2 — they register an already-created App (you supply app_id/installation_id/secrets/private key); the GitHub-side Manifest flow stays manual (already tracked in #7/#5, and #7's manifest-flow idea remains the real fix). Known upstream bugs: #10936 (PATCH rejects CUID2private_key_uuid; contributor has a draft PR), #5467 (create times out with many repos). Onnext,api_urlbecomes optional (derived fromhtml_url) and secrets become readable withread:sensitive.The DR row stands: the private key value and S3 access keys are credentials, not state — re-created by hand by nature, no API can change that.
10. Policy acts — for the whole picture
These appear in cast's output as "resolve manually (runbook act)" but are cast policy, not API gaps — the API could do all of them: orphan deletion (
DELETE /{resource}/{uuid}exists;applynever deletes by design), moving a resource between networks (PATCH acceptsdestination_uuidfor apps/services;applyrefuses by design),force_domain_override(deliberately never sent). No upstream ask needed; listed so the audit is the complete inventory of every "go do it in the UI" cast can emit.Feature requests to file upstream
Already covered — do not file:
next(34e6a6dd5); maintainer confirmed for v4.2 in discussion #10823; our #10948/#10949 already closed as redundant.next(#10405). Discussion #8827 ("REST API for Destinations") is still open and can get a "solved in v4.2" comment.Worth filing (nothing exists upstream today):
GET/POST /s3-storages, at minimum a team-scoped list returninguuid/name/endpoint) — the last resource cast needs that has zero API surface. It blocks both bootstrap automation (hand-copyings3_destination) and backup-target verification. Motivate it the way #10405 was motivated (IaC tools blocked); adjacent demand: discussion #7451, issues #6488, #9754.s3_storage_uuidonGET /databases/{uuid}/backups(or shape the response as an API resource) — writes already speak UUID; reads leak the internal int. One-line-ish serializer fix that makes backup targets round-trip verifiable.destination.uuidalongsidedestination_id, or include internalidin/destinationsresponses) — complements merged #10405; without it existing placements can be set and enumerated but never verified.Cast action items falling out of this audit
urlson 4.1.2 now (biggest UI-step deletion available today): give services a per-container domains vocabulary (mirror composeservice_domains), write viaurlson create/PATCH, readapplications[].fqdnback onGET /services/{uuid}, make it a diffed field; delete the warn inresolve.ts:673, the drop inserviceApiFields(), and the two UNCAPTURED rows. Correctsemantics.md's struck-through-precedent entry the same way #51 corrected the backup one.source_id↔GET /github-appsinstead of the only-App heuristic; correctsemantics.md/draft.tsclaims.draft.ts:594still says backup schedules "are not exposed by Coolify's API" (disproved by #51); NO_API_COVERAGE rows for service hostnames and GitHub Apps.next(34e6a6dd5serializessettingsonGET /applications/{uuid}); the fork'sGET .../settingssub-route branch is redundant; remaining work is the draft-honesty item plus post-v4.2 verification.custom_labels/ Basic Auth so a rebuilt resource isn't silently unprotected.read:sensitive(labels/compose reads move behind token abilities), then migrate: settings block into diffed fields (kills #2/#3 manual steps and un-degradesis_static), destinations resolver via/destinations(kills the hand-copieddestination_uuid).After all that, the irreducible manual set
With the 4.1.2-available fixes landed and a v4.2 upgrade: (a) create S3 storages in the UI and copy the UUID (until feature request 1 lands), (b) the GitHub-side App creation flow (#7's manifest flow shrinks it), (c) credential re-creation in DR (inherent), (d) cast's own deliberate runbook acts (orphan deletion, network moves, domain overrides — by design). Everything else cast currently tells the operator to do in the UI is automatable.
Verification notes: all v4.1.2 claims checked against tag
v4.1.2controllers/routes;nextclaims againstupstream/nextas of 2026-07-16 (7d699818e); upstream issue/PR landscape searched 2026-07-16.The cast-side action items in this audit are now individual issues; this issue stays the reference matrix, they carry the work:
urlson 4.1.2: hostnames as a diffed field, warn + UNCAPTURED rows deleted (finding 1; actionable today)source_id↔GET /github-apps, retire the only-App heuristic (finding 8; actionable today)GET /databases/{uuid}/backupsand emitsbackup:blocks; kills the last pre-#51 stale claim (draft.ts:594)read:sensitivetoken, OpenAPI re-vendor (findings 2, 3, 4 + the token-abilities regression)#70 was updated rather than closed: its upstream errand is obsolete (upstream shipped
34e6a6dd5onnext), but its draft-honesty fix and post-upgradeis_staticverification remain live there. The redundant fork branches (feat/api-get-application-settings,-v4x) are deleted.Upstream feature requests (S3 storages API,
s3_storage_uuidreadback, destination UUID on resource reads) are not yet filed — they live in this issue's "Worth filing" list until someone takes them to coollabsio/coolify.Status 2026-07-16 — everything solvable on 4.1.2 has landed. Leaving open; the remainder is v4.2-gated, deferred, or upstream.
Landed (merged today)
draft.ts:594backup claim (disproved by #51)cast diffphantom env driftBoth refuted claims are now corrected in
semantics.md#51-style, struck through rather than deleted — a limitation filed as a defect gets fixed; a defect filed as a limitation does not.Still open, deliberately
/backups. #81 left it in the identical position for service hostnames (API answers, sweep doesn't ask). Both are "one supplementary read per resource" — probably one piece of work.custom_labelsis unsound to manage at 4.1.2 (Coolify regenerates and clobbers it; the readonly flag isn't API-settable) and should likely leave scope; Basic Auth is sound but the password is a secret, so it needs age-store wiring, not a schema field.next(34e6a6dd5) still needs folding in.s3_storage_uuidreadback, destination UUID on resource reads) — not filed; left with the operator.Verification note
Every claim these PRs relied on was re-checked against the v4.1.2 source directly, not taken from the audit:
urlsin both allowlists +applyServiceUrls's name→ServiceApplicationmatch and its delete-on-conflict rollback for creates (ServicesController.php),$service->parse(isNew: true)running beforeapplyServiceUrls(so container names exist at create),source_id/source_typesurvivingremoveSensitiveData, andenvironment_detailsreturning applications un-stripped. The audit held on every point checked.One thing the audit's matrix understates: the only-App GitHub heuristic wasn't just unnecessary — it was silently wrong on a single-App instance, binding public repos (no App at all) to the only App. That's now pinned by a fixture.
Downstream: heavy-duty/incubator#17 migrates umami to
service_domains(the flat servicedomains#81 removed).Two follow-ups from this audit's implementation are now tracked rather than left implicit: #83 (
--emit-draftdoesn't make the per-service GET, so a drafted service has no hostnames — the twin of #75, which #81 exposed) and #84 (the semantics note that App registration is API-doable at 4.1.2 — #74's leftover box).Closing as complete — the audit's work is done or handed off, and a closed issue cites just as well.
Per the status comment above, everything solvable on 4.1.2 landed and is closed (#73, #76, #83, and the rest of that table). The remainder is v4.2-gated and tracked in #77, whose checklist also re-checks the two gaps that survive into v4.2 (S3 storages, backup S3-target readback). Nothing left here is a work order, and this board keeps only work orders open — the matrix stays the reference it was, from the closed state.