fix(deploy): preserve compose.override.yml when Mesh is enabled (#1420)

When a single-file stack is opted into Sencho Mesh, the deploy builds an
explicit `docker compose -f <base> -f <mesh override>` list. Passing any
explicit -f disables Compose's automatic discovery of compose.override.yml
(and the docker-compose.override variants), so a user's hand-authored
override was silently dropped from the effective deploy once Mesh was on.

Resolve the user's override file (first existing variant, with the same
stack-name and symlink-containment guards as the base compose file) and
insert it between the base and the mesh override, so it layers exactly as
Compose's implicit discovery would, with the mesh override still taking
precedence. A transient read failure during the lookup degrades to "no
override" rather than failing the deploy; a stack-name or containment-guard
rejection still aborts. Multi-file Git-source stacks and non-mesh deploys
are unaffected.
This commit is contained in:
Anso
2026-06-23 10:44:49 -04:00
committed by GitHub
parent b2713b8a4d
commit b753d2d5e0
5 changed files with 298 additions and 14 deletions
+22 -3
View File
@@ -117,10 +117,29 @@ export class ComposeService {
}
if (overridePath) {
if (filePrefix.length === 0) {
// Single-file stack: passing any -f disables auto-discovery, so name the
// base file explicitly before layering the override on top of it.
const baseFilename = await FileSystemService.getInstance(this.nodeId).getComposeFilename(stackName);
// Single-file stack: passing any -f disables compose's auto-discovery, so name
// the base file explicitly, then re-add the user's implicit override (if any) so
// it is not silently dropped, before layering the mesh override on top.
const fsSvc = FileSystemService.getInstance(this.nodeId);
const baseFilename = await fsSvc.getComposeFilename(stackName);
args.push('-f', baseFilename);
let userOverride: string | null = null;
try {
userOverride = await fsSvc.getOverrideFilename(stackName);
} catch (err) {
// Containment-guard rejections (bad stack name / symlink escape) are hard errors:
// abort the deploy rather than degrade. The "no override" case returns null rather
// than throwing, so any other throw is transient I/O: drop the override and proceed
// (logging the consequence) instead of failing the deploy.
const code = (err as { code?: string }).code;
if (code === 'INVALID_STACK_NAME' || code === 'INVALID_PATH' || code === 'SYMLINK_ESCAPE') {
throw err;
}
console.warn('[ComposeService] could not resolve user compose override; deploying without it:', sanitizeForLog((err as Error).message));
}
if (userOverride) {
args.push('-f', userOverride);
}
}
args.push('-f', overridePath);
}