Service hostnames ARE settable and readable on Coolify 4.1.2 (urls) — adopt it, delete the manual-UI step
#73
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#73
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?
Summary
The #72 audit refuted the load-bearing claim behind cast's biggest remaining manual step.
semantics.mdsays it 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 at tagv4.1.2:ServicesControlleracceptsurls— an array of{name, url}with comma-separated URLs per container — on bothPOST /services(allowlist + validation atServicesController.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— which cast never sends, per existing policy). Landed upstream 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. Create/PATCH responses even return'domains' => $service->applications()->pluck('fqdn').The original claim was half-technically-true (no field literally named
domains) and operationally false. It is the #51 arc repeating: a defect filed as a limitation never got re-checked.What is genuinely missing is on cast's side: the manifest's flat
services.<name>.domains: string[]has no per-container name to buildurls[]from. Compose applications already solve this exact shape withservice_domains(map of compose service name → URLs).What to do
service_domainsmap; decide migration story for the existing flatdomainsfield)urlson service create and PATCH (serviceApiFields()currently drops domains — delete the drop)fqdnfromGET /services/{uuid}into live fields (note: the list endpoint doesn't loadapplications; the per-uuid GET does)resolve.ts:673and the two UNCAPTURED.md rows ("service hostnames" inNO_API_COVERAGE, the per-service draft marker indraft.ts:616) — and have--emit-draftactually emit the hostnames it can now readsemantics.md's "Known limitations" entry the same way #51 corrected the backup one: struck through, kept, with the reason nobody lookedCaveats to carry into the implementation
urlsreplaces fqdn per named container. (nextaddsGET/PATCH /services/{uuid}/applications/{app_uuid}— nicer, but nothing here needs to wait for it.)preflightDomainConflictshandles apps; service writes can now hit the same refusal).Found in #72 (finding 1).
Done in #81 (merged,
bbca3cf).Services now speak the same per-container vocabulary a dockercompose app does:
service_domains: { <container>: [url] }; the flat, never-honoreddomainsis removed fromServiceSpec(it could not name the containerurls[]requires).serviceApiFieldsbuildsurlson create and update.GET /services/{uuid}per service (attachServiceDomains, gated todiff/applylike backups) projectsapplications[].fqdnback — so a declared hostname is compared every run. The manual-UI step is gone.desiredDomainsOfCreate— the more important because a conflicting service create is deleted server-side before the 409 (applyServiceUrlsrollback).service_domainsis canonicalized (keys + URL arrays sorted) on both sides, so container ordering never false-drifts.The warn at
resolve.ts:673, the drop inserviceApiFields(), and the UNCAPTURED/NO_API_COVERAGErows are deleted;semantics.md's struck-through-precedent entry is corrected #51-style.Two limits stated out loud rather than papered over:
GET /services/{uuid}aborts rather than projecting empty and re-PATCHing forever.inventory --emit-draftdoes not yet make the per-service GET, so a drafted service's hostnames are still declared by hand (same shape as backups — tracked in #75).Downstream sync: heavy-duty/incubator#17 migrates umami to
service_domains.The draft-capture half called out above is now tracked in #83.