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.ymlContainer 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;
    }
}
Why this works: Both containers are in the 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

Or browse free stack templates first →