Coolify enforces domain uniqueness across the whole instance; cast plans
inside one project + one environment. So apply could produce a plan that
was internally consistent, correct against everything cast can observe,
and still be refused — by a resource in a project cast never queries,
arriving as a raw 409 mid-apply, after the project and the environment
had already been created.
- Pre-flight the create plan: before the first write (project and
environment are created lazily, by the first create), check the domains
the plan is about to claim against GET /applications. A conflict is now
a refusal that costs nothing, not a half-applied run. One GET, and only
on a plan that creates an application with a domain — a first apply.
Covers both live shapes: fqdn, and per-service docker_compose_domains.
- Translate the 409 when one gets through anyway (a conflict with a
service fqdn or the instance fqdn is not visible in GET /applications,
so the pre-flight is a strict subset of Coolify's check). Names the
domain, the resource, its uuid — and whether it is outside the applied
project, which is the part the operator cannot get from Coolify.
- Never send force_domain_override=true. Coolify suggests it in the error
text; two resources on one domain is a routing coin-flip, and Coolify
says so in the same response.
Closes#44.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>