fix(mcp): inline magic env URLs in compose so service domains survive parse()
Coolify's Service::parse() (called inside the deploy job) iterates ALL
ServiceApplications in a multi-container template and overwrites
SERVICE_URL_<NAME> / SERVICE_FQDN_<NAME> env vars based on whichever
inner app's fqdn it sees first — frequently a worker still pointing at
the auto-generated *.sslip.io fallback. The result: the user's custom
domain is saved, the SPA loads, but its baked-in REACT_APP_SERVER_BASE_URL
points at sslip.io and every API call 404s.
apps_domains_set now substitutes ${SERVICE_URL_<NAME>} and
${SERVICE_FQDN_<NAME>} (with optional _<port> suffix) directly in the
docker_compose_raw before calling updateCompose(), and stops calling
service->parse() — so the literal URL survives any future redeploy.
Solves Twenty CRM domain bug; also unlocks reliable custom domains for
n8n, Plausible, Mautic, and every other Coolify service template that
follows the same SERVICE_URL_X pattern.
Made-with: Cursor
This commit is contained in:
@@ -2306,9 +2306,16 @@ async function toolAppsDomainsSet(principal: Principal, params: Record<string, a
|
||||
const fqdnsForUrl = normalized
|
||||
.map((d) => `https://${d}${port ? `:${port}` : ''}`)
|
||||
.join(',');
|
||||
// PHP code: load service, find target inner app by name (or auto-pick),
|
||||
// set fqdn, save, regenerate compose, re-parse. All quoting carefully
|
||||
// escaped because we shell this through ssh.
|
||||
// PHP code: load service, find target inner app, save fqdn, then
|
||||
// INLINE-REPLACE ${SERVICE_URL_<NAME>} / ${SERVICE_FQDN_<NAME>}
|
||||
// interpolations in docker_compose_raw with the literal user-provided
|
||||
// URL. This bypasses Coolify's parse() — which iterates ALL apps in
|
||||
// a multi-container template (twenty + worker + redis + postgres)
|
||||
// and overwrites SERVICE_URL_<NAME> env vars based on whichever
|
||||
// ServiceApplication.fqdn it sees first, often producing the
|
||||
// *.sslip.io fallback. Hardcoding the URL into the compose template
|
||||
// makes the deploy survive future parse() calls (which the deploy
|
||||
// job runs internally on every redeploy).
|
||||
const phpCode = `
|
||||
$service = App\\Models\\Service::where('uuid', '${appUuid}')->first();
|
||||
if (!$service) { echo 'service-not-found'; exit; }
|
||||
@@ -2317,8 +2324,42 @@ $target = ${inner === 'auto' ? '$apps->first(fn($a) => !str_contains(strtolower(
|
||||
if (!$target) { echo 'inner-app-not-found'; exit; }
|
||||
$target->fqdn = '${fqdnsForUrl}';
|
||||
$target->save();
|
||||
|
||||
// Parse the saved fqdn into base URL + bare host so we can substitute.
|
||||
$first = explode(',', $target->fqdn)[0];
|
||||
preg_match('#^(https?://)([^:/]+)(?::(\\\\d+))?#', $first, $mm);
|
||||
$scheme = $mm[1] ?? 'https://';
|
||||
$host = $mm[2] ?? '';
|
||||
$urlBase = $scheme . $host;
|
||||
$fqdnBase = $host;
|
||||
|
||||
// Compose template uses the inner app NAME uppercased, dashes→underscores.
|
||||
$svcVar = strtoupper(str_replace('-', '_', $target->name));
|
||||
$raw = $service->docker_compose_raw ?? '';
|
||||
|
||||
// Replace port-specific first so the bare match below doesn't eat the prefix.
|
||||
$raw = preg_replace_callback(
|
||||
'/\\\\$\\\\{SERVICE_URL_' . preg_quote($svcVar, '/') . '_(\\\\d+)\\\\}/',
|
||||
fn($m) => $urlBase . ':' . $m[1],
|
||||
$raw
|
||||
);
|
||||
$raw = preg_replace_callback(
|
||||
'/\\\\$\\\\{SERVICE_FQDN_' . preg_quote($svcVar, '/') . '_(\\\\d+)\\\\}/',
|
||||
fn($m) => $fqdnBase . ':' . $m[1],
|
||||
$raw
|
||||
);
|
||||
$raw = str_replace('\\\${SERVICE_URL_' . $svcVar . '}', $urlBase, $raw);
|
||||
$raw = str_replace('\\\${SERVICE_FQDN_' . $svcVar . '}', $fqdnBase, $raw);
|
||||
|
||||
$service->docker_compose_raw = $raw;
|
||||
$service->save();
|
||||
|
||||
// updateCompose() rewrites the rendered docker_compose output and the
|
||||
// SERVICE_URL_/SERVICE_FQDN_ env vars from $target->fqdn. We deliberately
|
||||
// SKIP $service->parse() because it picks the wrong inner app in
|
||||
// multi-container templates and reverts env vars to sslip.io.
|
||||
updateCompose($target);
|
||||
$service->parse();
|
||||
|
||||
echo 'fqdn-saved=' . $target->fresh()->fqdn;
|
||||
`;
|
||||
const result = await runOnCoolifyHost(
|
||||
|
||||
Reference in New Issue
Block a user