A runtime-agnostic Docker Compose parsing engine in Swift. It is the spec core
behind container-compose (a
compatibility layer for Apple's container),
but it depends only on Yams and knows nothing about any container runtime — so it
is usable on its own to read and reason about Compose files.
ComposeKit parses a Compose file, interpolates ${VAR} references, merges
.env with the environment, flattens include: and extends:, filters
services by profiles, and plans start order from depends_on. Mapping the
parsed model onto a specific container runtime lives in the consuming frontend.
API reference is generated with Swift-DocC and published to GitHub Pages: https://flaticols.github.io/ComposeKit/.
Build it locally:
swift package --disable-sandbox preview-documentation --target ComposeKit(The Docs workflow enables Pages automatically on first run.)
Sources/ComposeKit/
Model/
ComposeFile.swift # typed compose-spec subset
Scalars.swift # polymorphic decoders (string|list|map, ulimits, …)
Project.swift # locate + load + name + profile resolution
Profiles.swift # profile activation (which services are enabled)
Planner.swift # depends_on topological sort
Interpolation.swift # ${VAR} / .env expansion
Composition.swift # flatten include: + extends:
Merge.swift # deep-merge rules for extends/include
Sources/compose-validate/ # tiny CLI: does ComposeKit parse a file? (no deps)
Sources/compose-bench/ # lightweight micro-benchmarks
.package(url: "https://github.com/flaticols/ComposeKit.git", from: "0.0.2"),.target(
name: "YourTool",
dependencies: [.product(name: "ComposeKit", package: "ComposeKit")]
)import ComposeKit
let project = try Project.load(
explicit: nil, projectName: nil,
cwd: URL(fileURLWithPath: FileManager.default.currentDirectoryPath),
profiles: ["tools"])
let enabled = project.enabledServices() // honors profiles + depends_on
let order = try Planner.startOrder(project.file.services) // dependency order
.filter { enabled.contains($0) }Services with a profiles: key are enabled only when one of their profiles is
active — via the profiles: argument to Project.load or COMPOSE_PROFILES.
Unprofiled services always run; dependencies of an enabled service are pulled in
regardless. Project.enabledServices(explicit:) resolves the set.
Project.load flattens composition before the model is used:
include:merges other Compose files into the project (the including file wins on overlap); nested includes are resolved depth-first.extends:inherits another service's config, from the same file or another ({service, file}); cycles are detected.
Merge rules (override wins): scalars/objects replace, maps (environment,
labels, sysctls) merge by key, sequences (ports, volumes, …) concatenate,
and depends_on is unioned. Pragmatic and Compose-flavored rather than a full
implementation of every field-specific rule.
Project.load(files:) applies the same merge across multiple files (like
repeated -f), in order. With no files it auto-discovers the primary file and
merges a sibling compose.override.yaml if present (Docker-compatible).
A small CLI to check that a file parses:
swift run compose-validate compose.yaml # does it parse?
swift run compose-validate --profile tools compose.yaml # report profile-active servicesswift build
swift testLightweight, dependency-free micro-benchmarks (parse / interpolate):
swift run -c release compose-benchCI (.github/workflows/ci.yml) validates parsing
fidelity against external sources of truth:
- swift build/test on macOS (incl. a test that parses every file in
Tests/.../Fixtures/corpus). - Schema validation — every fixture is checked against the official
compose-spec JSON Schema, vendored under
Schema/. - docker parity —
Scripts/parity.shassertsdocker compose configandcompose-validateboth accept each corpus file.
A scheduled workflow (nightly-schema.yml)
refreshes the vendored schema from upstream each night, re-validates the fixtures
and runs the tests, and opens a PR when the spec changed — so spec drift surfaces
early.
Apache-2.0 (matches the upstream container project).