fix(apps): compose-aware domains; loud apps.update ignore list
Two live-test bugs surfaced while deploying Twenty CRM:
1. apps.domains.set silently 422'd on compose apps
Coolify hard-rejects top-level `domains` for dockercompose build
packs — they must use `docker_compose_domains` (per-service JSON).
setApplicationDomains now detects build_pack (fetched via GET if
not passed) and dispatches correctly. Default service is `server`
(matches Twenty, Plane, Cal.com); override with `service` param.
2. apps.update silently dropped unrecognised fields
Caller got `{ok:true}` even when zero fields persisted. This
created false-positive "bug reports" (e.g. the user-reported
"fqdn returns ok but doesn't persist" — fqdn was never forwarded
at all). apps.update now returns:
- applied: fields that were forwarded to Coolify
- ignored: unknown fields (agent typos, stale field names)
- rerouted: fields that belong to a different tool
(fqdn/domains → apps.domains.set,
git_repository → apps.rewire_git)
400 when nothing applied, 200 with diagnostics otherwise.
Made-with: Cursor
This commit is contained in:
@@ -468,7 +468,23 @@ export async function updateApplication(
|
||||
export async function setApplicationDomains(
|
||||
uuid: string,
|
||||
domains: string[],
|
||||
opts: { forceOverride?: boolean } = {}
|
||||
opts: {
|
||||
forceOverride?: boolean;
|
||||
/**
|
||||
* Build pack of the target app. Required to dispatch to the right
|
||||
* Coolify field:
|
||||
* - dockercompose → docker_compose_domains (per-service JSON)
|
||||
* - everything else → domains (comma-separated string)
|
||||
* If omitted we GET the app and detect it.
|
||||
*/
|
||||
buildPack?: string;
|
||||
/**
|
||||
* For compose apps only: which compose service should receive the
|
||||
* public domain(s). Defaults to 'server' (matches Twenty, Plane,
|
||||
* Cal.com). Ignored for non-compose apps.
|
||||
*/
|
||||
composeService?: string;
|
||||
} = {}
|
||||
): Promise<{ uuid: string }> {
|
||||
// Coolify validates each entry as a URL, so bare hostnames need a scheme.
|
||||
const normalized = domains.map(d => {
|
||||
@@ -476,16 +492,34 @@ export async function setApplicationDomains(
|
||||
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
||||
return `https://${trimmed}`;
|
||||
});
|
||||
// Coolify API: send `domains` (NOT `fqdn`). The controller maps it to
|
||||
// the DB's `fqdn` column internally, but only when the destination
|
||||
// server has `proxy.type=TRAEFIK` (or CADDY) AND `is_build_server=false`
|
||||
// — i.e. when Server::isProxyShouldRun() returns true. If either is
|
||||
// misconfigured, the controller silently drops the field (PATCH returns
|
||||
// 200, fqdn unchanged). We hit this on the missinglettr-test app on
|
||||
// 2026-04-22; the underlying server had proxy.type=null and
|
||||
// is_build_server=true. Fix is in Coolify server-config (UI/DB), not
|
||||
// the client. Sending `fqdn` directly is rejected with 422 ("This
|
||||
// field is not allowed").
|
||||
|
||||
let buildPack = opts.buildPack;
|
||||
if (!buildPack) {
|
||||
const app = await getApplication(uuid);
|
||||
buildPack = (app.build_pack ?? 'nixpacks') as string;
|
||||
}
|
||||
|
||||
// ── Compose apps: set per-service domains ──────────────────────────
|
||||
// Coolify hard-rejects the top-level `domains` field for dockercompose
|
||||
// (HTTP 422: "Use docker_compose_domains instead"). Domains live per
|
||||
// compose service — we default to the `server` service, which matches
|
||||
// the majority of self-hostable apps (Twenty, Plane, Cal.com, etc.).
|
||||
if (buildPack === 'dockercompose') {
|
||||
const service = (opts.composeService ?? 'server').trim();
|
||||
// Coolify accepts an array of {name, domain}; ONE entry per service.
|
||||
// Multiple domains → comma-join into the single service's `domain`
|
||||
// field (Coolify splits on comma internally).
|
||||
const payload = [{ name: service, domain: normalized.join(',') }];
|
||||
return updateApplication(uuid, { docker_compose_domains: payload });
|
||||
}
|
||||
|
||||
// ── Single-container apps: top-level `domains` (maps to fqdn) ──────
|
||||
// Coolify maps `domains` → the DB `fqdn` column, but only when the
|
||||
// destination server has `proxy.type=TRAEFIK`/`CADDY` AND
|
||||
// `is_build_server=false` (Server::isProxyShouldRun() returns true).
|
||||
// If either is misconfigured, the PATCH silently drops the field
|
||||
// (200 OK but fqdn unchanged). Fix that in Coolify's server config,
|
||||
// not here. Sending `fqdn` directly returns 422.
|
||||
return updateApplication(uuid, {
|
||||
domains: normalized.join(','),
|
||||
force_domain_override: opts.forceOverride ?? true,
|
||||
|
||||
Reference in New Issue
Block a user