src/compose owns Docker Compose source discovery, source loading, stack file generation,
Compose-specific merging, override application, Swarm compatibility transforms, named volume
declaration, reload orchestration, sync validation, and operation planning for compose-related
workflows. It converts per-service docker-compose.yml or docker-compose.yaml files tagged with
x-stack metadata into canonical stack YAML files under the configured stack directory, then feeds
those files into render and Docker deployment flows where applicable.
Core public entry points are:
discoverComposeFiles(options: DiscoverOptions): Promise<DiscoverResult>indiscover.ts.loadCompose(path: string): Promise<LoadResult>andloadFragment(directory: string)inload.ts.generateStacks(options: GenerateOptions): Promise<GenerateResult>ingenerate.ts.composeDeepMerge(base, override)inmerge.ts.composeOverrideMerge(base, override),loadOverrideFile(path, repoRoot), andapplyOverrides(baseCompose, overrides, repoRoot)inoverride.ts.stripComposeOnlyKeys,applyLoggingDefaults,rewriteEnvFile, andrewriteBindMountPathsintransform.ts.collectAllNamedVolumesandcollectNamedVolumesinvolumes.ts.reloadStacks(options: ReloadOptions): Promise<ReloadResult[]>inreload.ts.sync(opts: SyncOptions): Promise<SyncResult>insync.ts.planOperation(opts: PlanOptions): Promise<PlanResult>inplan.ts.
- Pipeline orchestration:
generateStackscomposes discovery, loading, fragment merging, override merging, service transforms, named volume collection, and YAML serialization into one generation pipeline.reloadStacks,sync, andplanOperationreuse that pipeline instead of duplicating low-level compose logic. - Pure merge and transform helpers:
composeDeepMerge,composeOverrideMerge,stripComposeOnlyKeys,applyLoggingDefaults,rewriteEnvFile,rewriteBindMountPaths,collectNamedVolumes, andcollectAllNamedVolumesreturn new objects or arrays and avoid mutating their inputs. - Separate merge semantics:
composeDeepMergeis used for composing source files andswarm.fragment.ymlsidecars, with arrays replaced.composeOverrideMergemodels Docker Compose override semantics, with arrays appended. - Metadata normalization boundary:
normalizeStackNameaccepts legacy scalarx-stack: nameand object formx-stack: { name: value }, rejects empty names, unknown object fields, and invalid shapes, and removesx-stackfrom loaded compose data vialoadCompose. - Safety modes:
GenerateOptions.dryRun,ReloadOptions.dryRun,ReloadOptions.skipUnchanged, and planning dry runs are used to prevent writes or deployments when callers need previews or validation only. - Typed result envelopes: workflows return structured result objects such as
GenerateResult,ReloadResult,SyncResult, andPlanResult, with warnings and errors collected as data rather than thrown across the top-level operation boundary. - Barrel exports:
mod.tsre-exports compose types and most submodule APIs, plusgenerateStacksand the generation option/result types.
Discovery and loading:
discoverComposeFileswalksDiscoverOptions.repoRootwith@std/fs/walk, includes files only, skips hidden directories, and filters names todocker-compose.ymlordocker-compose.yaml.DEFAULT_SKIP_DIRSexcludesnode_modules,stacks,tools,environments, and__pycache__;DiscoverOptions.skipDirsextends that set.- Each candidate YAML file is parsed with
@std/yaml.parse. Files withoutx-stackare ignored. Invalid YAML or invalid stack metadata are recorded inDiscoverResult.errors. normalizeStackNameconverts thex-stackvalue to the grouping key, producingDiscoverResult.stacksasRecord<string, string[]>.loadComposeparses a selected compose file, validatesx-stack, removes it from the returnedLoadResult.data, and returnsLoadResult.stackName.loadFragmentresolvesswarm.fragment.ymlin the compose directory, returns{}when absent, and parses it when present.
Generation, merging, overrides, transforms, and volume handling:
generateStacksdiscovers compose files, selectsGenerateOptions.stacksor all discovered stack names, creates the output directory unlessdryRunis enabled, and calls the internalgenerateSingleStackfor each target.generateSingleStackloads every compose source and itsswarm.fragment.ymlsidecar. For each source it merges compose data with the fragment throughcomposeDeepMerge, then merges all sources together throughcomposeDeepMergeagain.- If
GenerateOptions.overridesis present,applyOverridesresolves each string orOverrideEntry, loads it throughloadOverrideFile, and applies entries left to right throughcomposeOverrideMerge. - Service transforms run per service in this order:
stripComposeOnlyKeysremoves Swarm-invalidcontainer_name,restart, andbuild;applyLoggingDefaultsadds the local logging driver defaults when no logging block exists;rewriteEnvFilerewrites relativeenv_fileentries to repo-root-relative paths;rewriteBindMountPathsrewrites relative bind mount sources to repo-root-relative paths. rewriteBindMountPathshandles short-form volume strings by recognizing sources that start with.,/, or~, and handles long-form mounts by rewritingsourcewhentypeisbindor absent. Long-formtype: volumemounts are left unchanged.collectAllNamedVolumesscans all transformed services and usescollectNamedVolumesto find named volumes. Short-form named volumes are sources that do not start with.,/, or~. Long-form named volumes requiretype === "volume"and a stringsource. The generated stack declares each deduplicated, sorted named volume as{ external: true }.- The output stack always declares the default network as external
traefik-public, emits services when present, emits external named volumes when found, serializes with@std/yaml.stringify, and writes<outputDir>/<stackName>.ymlunlessdryRunis enabled.
Reload:
reloadStacksreceives a pre-resolvedResolvedConfig, aProcessRunner, and optional stack, override, dry run, log, checksum, and force-update flags.- It computes
repoRoot, stack directory, render directory, and target stacks from config and options, then concatenates config overrides with CLI overrides. - Unless
skipGenerateis true, it callsgenerateStackswithdryRun: falseso the stack files exist for rendering. - For each stack it reads
<stacksDir>/<stackName>.yml, parses YAML toComposeData, callsrenderStackwith strict interpolation, serializes rendered YAML, and targets<renderDir>/<stackName>.rendered.yml. - When
skipUnchangedis true,computeSha256andunchangedCheckcompare the new rendered content to the previous rendered file. Matching stacks returnunchangedorwould-skip. - Non-dry runs write the rendered file and call
dockerStackDeploywithprune: falseandresolveImage: "always". Dry runs returnwould-deploy. - If enabled,
forceServiceUpdatelists services throughdockerStackServices, parses JSON lines, and callsdockerServiceUpdatewith{ force: true }.followLogsusesdockerServiceLogsfor deployed stack services on a best-effort basis.
Sync:
syncresolves config throughresolveConfig, discovers compose files, and selects requested stacks or discovered stacks.- It calls
generateStackswithdryRun: true, then compares each generated YAML string against the canonical<stacksDir>/<stackName>.ymlfile. - Differences set
SyncResult.matchto false and populateSyncResult.diffswith a lightweight textual diff fromgenerateDiff, built usinglcsFn. Sync never renders and never deploys.
Planning:
planOperationresolves config, createsPlanResultand stablePlanJsonOutput, and always reports configuration and compose discovery sections.planComposeDiscoveryusesdiscoverComposeFilesto show discovered stack files and missing requested stacks.planOverrideslists explicit override paths.- For
up,sync,generate,reload, andall,planGenerationcallsgenerateStackswithdryRun: trueand lists stack files that would be generated. - For
up,sync,render,reload, andall,planRenderparses generated YAML, callsrenderStack, counts interpolation sources from environment values andenv_file, and reports rendered output paths. planDockerCommandsproduces non-executed Docker command strings forup,sync,down,reload, andall.planEnvandplanSecretsadd environment and secrets sections for relevant operations.planSecretsonly discovers encrypted inputs and cleanup actions; it does not decrypt files.
- Config:
syncandplanOperationcallresolveConfigfromsrc/config/load.ts.reloadStacksaccepts aResolvedConfigfrom the CLI layer.GenerateOptions.overrides,ReloadOptions.overrides, andPlanOptions.overridesuseOverrideEntryfromsrc/config/types.ts. - Render:
reloadStacksandplanRendercallrenderStackfromsrc/render/mod.tsto interpolate${VAR}placeholders after stack generation. - Docker:
reloadStackscallsdockerStackDeploy,dockerStackServices,dockerServiceUpdate, anddockerServiceLogsfromsrc/docker/mod.tsthrough aProcessRunnerabstraction. - Process: Docker commands run through
ProcessRunnerfromsrc/process/types.ts, includingrunner.withDryRun(true)for dry-run reload behavior. - Env:
planEnvdynamically importsdiscoverEnvExamplesfromsrc/env/mod.tswhen planningenvoralloperations. - Secrets:
planSecretsdynamically importsfindEncryptedEnvFilesfromsrc/secrets/mod.tsand intentionally limits itself to discovery and cleanup planning. - Standard library: modules use
@std/yamlfor parsing and serialization,@std/pathfor path resolution and joining,@std/fsfor walking, existence checks, and output directory creation, and Deno file APIs for reading and writing YAML files.