Part 2 of 3
Compose Is Missing — Here's What Actually Works
Container Machines v1.0 + a shell script + DNS config = your multi-container setup, running. No compose required.
The Problem
Docker Compose doesn't exist for Apple Container. GitHub issues #55 and #186 are the most-requested features. The docker/compose repo has an open issue (#12934) requesting Apple Container support.
Apple hasn't committed to a timeline. But you can run multi-container setups today using Container Machines — the v1.0 feature that most tutorials don't even mention.
What Are Container Machines?
Container Machines is Apple Container's built-in mechanism for grouping multiple containers. Think of it as a lightweight alternative to docker-compose: you define a group of containers, their images, mounts, environment variables, and networking — then start them all with one command.
A Container Machine is defined via CLI flags (v1.0) — no YAML file yet, but the structure maps cleanly from docker-compose. Here's the mapping:
| docker-compose.yml | Container Machine |
|---|---|
services: | Containers within the machine group |
image: | --image flag per container |
ports: | Not needed — each container has its own IP (no port mapping) |
volumes: | --mount type=bind/volume,... flag per container |
environment: | --env KEY=val flags per container |
depends_on: | Shell script controls startup order |
networks: | All containers in a machine share the DNS domain (dev.internal) |
Real Example: Nginx + Node.js API
Here's a typical web stack. We'll convert it from docker-compose to Apple Container.
Original docker-compose.yml
version: '3'
services:
api:
image: myapp-api:latest
environment:
- PORT=3000
- NODE_ENV=production
volumes:
- ./src:/app/src
ports:
- "3000:3000"
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
depends_on:
- api
Step 1: Start Both Containers in a Machine Group
# Create and start the machine group
container machine create web-stack
# Start the API container
container run --name api --machine web-stack \
--env PORT=3000 --env NODE_ENV=production \
--mount type=bind,src=$(pwd)/src,dst=/app/src \
myapp-api:latest
# Start the Nginx container
container run --name nginx --machine web-stack \
--mount type=bind,src=$(pwd)/nginx.conf,dst=/etc/nginx/nginx.conf \
nginx:latest
Step 2: Configure nginx.conf for Inter-Container Routing
# nginx.conf — proxy to the API container via DNS
server {
listen 80;
server_name _;
location / {
proxy_pass http://api.dev.internal:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
web-stack machine group and share the dev.internal DNS domain. Nginx reaches the API container at api.dev.internal:3000 — no port mapping needed. Each container has its own IP on the virtual network.
Step 3: Automate With a Shell Script
Wrap the commands in a script that handles startup order — this is your compose replacement:
#!/bin/bash
# start-stack.sh — Launch the web-stack machine group
set -e
echo "==> Creating machine group: web-stack"
container machine create web-stack 2>/dev/null || true
echo "==> Starting API container"
container run --name api --machine web-stack \
--env PORT=3000 --env NODE_ENV=production \
--mount type=bind,src=$(pwd)/src,dst=/app/src \
myapp-api:latest
echo "==> Waiting for API to be ready"
sleep 3
echo "==> Starting Nginx container"
container run --name nginx --machine web-stack \
--mount type=bind,src=$(pwd)/nginx.conf,dst=/etc/nginx/nginx.conf \
nginx:latest
echo "==> Stack running. Check status:"
container machine status web-stack
echo "API: http://api.dev.internal:3000"
echo "Nginx: http://nginx.dev.internal"
Make it executable and run:
chmod +x start-stack.sh
./start-stack.sh
Step 4: Verify Everything Works
# Check all containers in the machine group
container machine status web-stack
# Output should show:
# MACHINE CONTAINER STATUS IP
# web-stack api running 10.0.0.2
# web-stack nginx running 10.0.0.3
# Test the API directly
curl http://api.dev.internal:3000
# Test via Nginx
curl http://nginx.dev.internal
If both return expected responses, you've just replaced docker-compose with a working multi-container Apple Container setup.
Caveats & Limitations
No healthcheck built-in
docker-compose has healthcheck:. With Container Machines, add a polling loop in your start script (e.g., until curl -s http://api.dev.internal:3000/health; do sleep 1; done).
Startup order is manual
depends_on doesn't exist. Your shell script controls order — sleep durations or polling loops are your friend. The full manual includes robust health-check wrappers.
No YAML config
v1.0 is CLI-only. No declarative config file. Keep your startup script in version control — it is your config. A YAML parser may come in a future Apple Container release.
Want the Full Migration Manual?
The compose alternative above works, but it's the tip of the iceberg. The manual covers production hardening, 5 stack templates, and the conversion script pack.
Get the Complete Manual — $29