Bringing Docker Compose-style workflows to Apple Container
Apple’s container project is one of the most interesting moves in the local development ecosystem in years.
For a long time, running containers on macOS meant accepting a stack that was not really native to the platform: virtual machines, daemons, file-sharing layers, networking layers, and developer tooling built on top. That worked, but it came with trade-offs.
Apple’s container changes the discussion. It is written in Swift, designed for Apple Silicon, and built around running Linux containers as lightweight virtual machines on macOS. It works with OCI-compatible images, so it stays connected to the broader container ecosystem instead of creating a closed island.
But there was one obvious gap for daily development: single containers are useful, while real applications are rarely single containers.
Most modern development environments are composed of several services: an API, a database, a queue, a worker, a reverse proxy, a metrics stack, or a migration job. For many teams, docker compose up is not just a command. It is the entry point to the project.
That is why we started container compose.
The problem: teams need composition
Apple’s container CLI gives macOS developers a native way to build and run containers. That is already significant.
But developer workflows usually need orchestration at the project level.
A typical application does not only say:
run this imageIt says:
start Postgreswait until it is healthythen start the APImount these filesexpose these portsrun this service only in debug modescale this workerstream logs for this projecttear everything down cleanlyThat is the layer Docker Compose made familiar.
Our goal with container compose is to bring that kind of workflow to Apple’s container ecosystem while staying aligned with the way Apple’s runtime actually works.
Not a Docker daemon clone.
Not a full Kubernetes layer.
A native plugin for Apple’s container CLI that understands Compose-like development workflows.
What we built
container compose is a native plugin for Apple’s container CLI.
It lets you start, stop, inspect, and manage multi-container applications from standard Compose files:
container system startcontainer compose up -f compose.yaml -p democontainer compose ps -p democontainer compose logs -p democontainer compose down -p demoThe intent is simple: keep the workflow developers already understand, but make it work against Apple’s container runtime.
The plugin supports the core lifecycle commands developers expect:
container compose upcontainer compose downcontainer compose pscontainer compose logscontainer compose execcontainer compose runcontainer compose configcontainer compose watchcontainer compose statsIt also includes operational workflows such as saving and loading a stack archive for offline migration.
This matters because local development is not only about starting containers. It is about repeatability: inspect what is running, validate the resolved configuration, stream logs, execute commands inside services, copy files, observe resource usage, and cleanly remove project resources.
Why a plugin, not a wrapper?
The easy path would have been a thin wrapper around command-line calls.
We did not want only that.
A wrapper can hide complexity for simple demos, but it usually breaks down when real workflows appear: dependency ordering, health checks, profiles, scaling, project naming, environment substitution, machine contexts, volumes, secrets, and failure handling.
container compose is written in Swift and built against Apple’s container ecosystem. The package depends on Apple’s container, containerization, and related Swift packages, plus swift-argument-parser and Yams for CLI and YAML parsing.
That gives us a better foundation than shelling out blindly.
The plugin can parse and resolve Compose files, build an execution plan, validate constraints early, and then drive the container runtime intentionally.
Startup is not just “start everything”
One of the most important parts of Compose-style orchestration is startup order.
Starting ten containers in random order is easy. Starting them correctly is harder.
container compose starts services in dependency waves. Services in the same wave can start in parallel. The next wave starts only when the previous dependency requirements are satisfied.
For example:
services: db: image: postgres:16 healthcheck: test: ["CMD", "pg_isready"] interval: 5s timeout: 3s retries: 5
api: image: myapp:latest depends_on: db: condition: service_healthyIn this case, the API should not start just because the database process exists. It should start when the database is actually healthy.
The plugin supports dependency conditions such as service_started, service_healthy, and service_completed_successfully. The last one is especially useful for one-shot services such as migrations.
This is the difference between a local environment that usually works and a local environment that is deterministic enough for teams.
Project-level semantics
Compose workflows need a project model.
A project is the boundary that tells the tool which containers belong together. Without that, commands like down, ps, logs, and stats become fragile.
container compose resolves project names from the -p flag, COMPOSE_PROJECT_NAME, the name: field in the Compose file, or the parent directory name.
That means this:
container compose up -p democontainer compose logs -p democontainer compose down -p demooperates on the same logical stack.
Containers follow a predictable naming convention:
{project}_{service}_{index}For example:
demo_web_1demo_web_2demo_db_1That predictability matters when debugging, scripting, and explaining the local environment to another developer.
Compose file resolution
The plugin supports the standard discovery path developers expect:
compose.yamlcompose.ymldocker-compose.yamldocker-compose.ymlIt also supports multiple files:
container compose up -f base.yml -f production.ymlLater files override earlier ones.
This is essential for real projects, where a team may have a base stack, a development override, a CI override, or a local override.
The plugin also supports variable substitution from .env files and the shell environment with familiar syntax:
image: ${IMAGE_NAME:-nginx:latest}That keeps Compose files portable without hardcoding every local value.
Profiles, scaling, and resource limits
Modern development stacks often contain services that should not start every time.
A debugger, metrics exporter, admin UI, or local-only helper may be useful, but only when explicitly requested.
Profiles handle that:
services: app: image: myapp:latest
debugger: image: mytools:latest profiles: [debug]Then:
container compose upstarts the default app, while:
container compose up --profile debugstarts the optional debugger too.
Scaling is also supported:
container compose up --scale web=5The plugin validates cases where scaling cannot work, such as multiple replicas trying to bind the same static host port.
Resource limits are supported through deploy.resources.limits, with validation aligned to Apple’s container model.
Working with Apple container machines
Apple’s container ecosystem includes container machines, and container compose supports running a project inside an existing machine through --machine.
For example:
container machine create --name devcontainer compose up --machine dev -f compose.yaml -p democontainer compose ps --machine dev -p democontainer compose logs --machine dev -p democontainer compose down --machine dev -p demoThis gives the plugin two execution contexts: the application sandbox and a container machine.
That distinction matters because not every feature behaves the same way in both contexts. The plugin is explicit about those constraints instead of pretending the environments are identical.
Observability for local orchestration
Local tooling should be debuggable.
When something fails during startup, developers need to understand what happened: which service started, which dependency failed, which wave rolled back, which network or volume operation ran, and where time was spent.
container compose emits orchestration events to macOS Unified Logging under:
com.simplifi-ed.container-composeYou can inspect those events from Console.app or from the terminal:
log stream --predicate 'subsystem == "com.simplifi-ed.container-compose" AND category == "orchestration"'The plugin also emits Instruments signposts for startup hot paths such as parse, plan, network creation, volume creation, and execution waves.
This reflects a design principle: local infrastructure should be observable too.
Host DNS for local development
Local development often needs stable hostnames.
Ports are useful, but hostnames make browser-based testing, callbacks, cookies, and local multi-service routing easier.
container compose includes an opt-in host DNS feature:
services: web: image: nginx:1.27.3 ports: - "8080:80" x-compose: hosts: - web.demo.localThen:
container compose up --host-dnsmaps the declared hostname to 127.0.0.1 on the macOS host.
The plugin stays unprivileged. macOS asks for permission only when writing to /etc/hosts, and container compose down removes the project block when possible.
Offline stack migration
Another feature we wanted early was the ability to package a resolved stack.
container compose save can bundle manifest.json, compose.yaml, and images.tar into a portable archive:
container compose save -f compose.yaml -o stack.tarThen another machine can load it:
container compose load -i stack.tarcontainer compose up -f compose.yamlThis does not try to solve every migration problem. It does not package volumes, container filesystem state, registry push/pull, or secrets. It focuses on a practical boundary: resolved Compose configuration plus local OCI images.
That is enough for many offline, demo, workshop, and controlled-environment workflows.
What this is not
This project is not a claim that Apple Container should replace every existing container workflow today.
It is also not a claim that Docker Compose semantics can be copied perfectly into a different runtime model.
Apple’s model has different constraints. Some are architectural. Some are maturity-related. Some are simply the result of building on a new ecosystem.
So the right approach is not blind compatibility.
The right approach is pragmatic compatibility:
- keep the Compose file model where it makes sense
- preserve familiar commands where possible
- validate unsupported cases early
- make runtime-specific behavior explicit
- use native macOS capabilities instead of fighting them
That is the line we are trying to walk.
Contributions are open
This is an open project, and contributions are welcome.
The goal is not to build a closed internal tool for one team. The goal is to explore what Compose-style orchestration should look like on top of Apple Container, with real feedback from developers who are trying to use it on real projects.
Contributions can take several forms: testing the plugin on existing Compose files, reporting unsupported Compose features, improving compatibility with common development stacks, refining macOS-specific behavior, improving documentation and examples, proposing better defaults, contributing Swift code, or helping define what native Compose-like workflow on Apple Container should become.
Apple Container is still a young ecosystem. The best time to shape the developer experience is now, before patterns become accidental standards.
The repository is open:
https://github.com/Simplifi-ED/composeIssues, ideas, tests, and pull requests are welcome.
Open source between Europe and Africa
container compose is one project in a broader open source effort we are building at Simplifi’ED and Omnivya.
We believe open source is not only about publishing repositories. It is about creating technical bridges: between teams, between ecosystems, and between regions that often solve similar engineering problems in different contexts.
That matters to us because we work between Europe and Africa.
The constraints are real on both sides: developer experience, infrastructure cost, sovereignty, talent development, cloud maturity, security, local regulations, and the need to build software that can survive outside perfect lab conditions.
Open source gives us a practical way to share those lessons. It lets us publish tools, document decisions, invite feedback, and discuss architecture with engineers, companies, schools, and communities beyond one commercial boundary.
We have already created other open source projects, and we are happy to talk about them: what worked, what failed, what should be improved, and where collaboration makes sense.
Why this matters
The developer workstation is changing again.
For years, the default answer on macOS was to run a Linux container engine through a virtualization layer and then build the developer workflow on top of it.
Apple is now exposing a more native foundation.
That does not automatically replace the ecosystem around containers. The ecosystem is not just image execution. It is orchestration, configuration, logs, health checks, profiles, volumes, secrets, networking, local DNS, offline workflows, and developer muscle memory.
container compose exists because the next generation of macOS container tooling needs that layer.
We want developers to be able to keep writing:
container compose upand get a real application stack, not just a single container.
Current status
The project is active and evolving.
Today, it already covers a significant subset of Compose-style local orchestration: lifecycle commands, dependency waves, health-based startup, project naming, config resolution, profiles, scaling, resource limits, logs, exec, stats, watch, save/load, host DNS, and support for Apple container machines.
There are still limits. Some are documented. Some will change as Apple’s container ecosystem matures. Some will require deliberate design decisions because native macOS containerization is not the same architecture as Docker Desktop.
That is exactly why we started now.
The earlier this layer exists, the sooner teams can experiment with Apple’s container runtime using workflows close to what they already know.
Try it
Install the plugin through the Simplifi-ED Homebrew tap:
brew tap Simplifi-ED/composebrew trust --formula simplifi-ed/compose/container-composebrew install simplifi-ed/compose/container-composeStart Apple’s container runtime:
container system startThen run a Compose stack:
container compose up -f compose.yaml -p demoInspect it:
container compose ps -p democontainer compose logs -p democontainer compose stats -p demoTear it down:
container compose down -p demoThis is the beginning of a more native container development workflow on macOS.
Not by abandoning Compose-style ergonomics.
By bringing them to Apple Container, in the open, with the community.