DEV Community

Figsy
Figsy

Posted on

Apple's new `container` tool — worth a look

Apple open-sourced a native container runtime for macOS (announced WWDC 2025, written in Swift). A few things that make it interesting:

One lightweight VM per container

Unlike Docker Desktop, which runs everything in a single shared Linux VM, each container gets its own micro-VM that boots in under a second. Stronger isolation, and no big always-on VM eating RAM.

Optimized for Apple Silicon

It's built on Apple's Virtualization framework, so it's fast and battery-friendly on M-series Macs.

Fully OCI-compatible

It pulls standard images from Docker Hub or any registry — container run nginx:alpine just works. Dockerfiles build as usual.

Each container gets its own IP

(on macOS 26), so you often don't even need port mapping — no more -p 8080:80 conflicts.

But there's no docker compose?

This turns out we don't really need it for local dev. Compose mostly gives us service discovery + one-command startup, and both are easy here:

sudo container system dns create test
container system property set dns.domain test
Enter fullscreen mode Exit fullscreen mode

Now every named container resolves as <name>.test — e.g. the API just talks to db.test. A ~10-line shell script starts the stack in order, replacing compose up. And if we ever want real compose files, community tools (Container-Compose, socktainer) bridge that.

Free, open source, no Docker Desktop license. Could be worth trialing on a non-critical project. Repo: github.com/apple/container

Two Services Talking to Each Other with Apple container (A minimal tutorial)

An api service and a web service, running in Apple's native container tool, calling each other by DNS name (api.test) — no Docker, no Compose.

┌─────────────┐         ┌──────────────────┐
│   Browser    │ ──────▶ │  web (port 8080) │
│  localhost   │         │  fetches from:   │
└─────────────┘         │  http://api.test │
                         └────────┬─────────┘
                                  │ DNS: api.test
                         ┌────────▼─────────┐
                         │  api (port 8000) │
                         └──────────────────┘
Enter fullscreen mode Exit fullscreen mode

Prerequisites

  • Apple Silicon Mac, macOS 26 (Tahoe) recommended for full container-to-container DNS
  • Apple container v1.0+ installed (container --version)

Step 1 — One-time DNS setup

Create a local DNS domain so containers can resolve each other as <name>.test:

sudo container system dns create test
Enter fullscreen mode Exit fullscreen mode

Set it as the default domain. Since v1.0 this lives in a TOML config file (the old container system property set command was removed):

mkdir -p ~/.config/container
cat >> ~/.config/container/config.toml << 'EOF'
[dns]
domain = "test"
EOF
Enter fullscreen mode Exit fullscreen mode

Restart the service so the config is picked up:

container system stop
container system start
Enter fullscreen mode Exit fullscreen mode

Verify:

container system property list
# [dns]
# domain = "test"
Enter fullscreen mode Exit fullscreen mode

Step 2 — Start the API service

A tiny HTTP API that returns a JSON message on port 8000:

container run -d --name api python:3-alpine sh -c '
  echo "{\"message\": \"hello from api\"}" > /tmp/index.html &&
  python3 -m http.server 8000 -d /tmp
'
Enter fullscreen mode Exit fullscreen mode

The container is now reachable from other containers at http://api.test:8000. No port publishing needed — every container gets its own IP.

Step 3 — Start the web service (calls the API by name)

A web app that fetches from api.test and serves the result to your browser:

container run -d --name web -p 8080:80 python:3-alpine sh -c '
  pip install --quiet flask requests &&
  cat > /app.py << "PY"
from flask import Flask
import requests

app = Flask(__name__)

@app.route("/")
def home():
    r = requests.get("http://api.test:8000")   # <-- DNS name, not an IP!
    return f"<h1>web service</h1><p>api.test said: {r.text}</p>"

app.run(host="0.0.0.0", port=80)
PY
  python3 /app.py
'
Enter fullscreen mode Exit fullscreen mode

The key line is requests.get("http://api.test:8000") — the web container finds the api container purely by its DNS name, exactly like Compose service names.

Step 4 — Test it

From your Mac:

curl http://localhost:8080
# <h1>web service</h1><p>api.test said: {"message": "hello from api"}</p>
Enter fullscreen mode Exit fullscreen mode

Or open http://localhost:8080 in your browser.

You can also poke around from inside a throwaway container:

container run -it --rm alpine sh
/ # wget -qO- http://api.test:8000
{"message": "hello from api"}
Enter fullscreen mode Exit fullscreen mode

Step 5 — Day-to-day commands

container ls                 # running containers
container ls -a              # including stopped ones
container logs web           # view output
container stop api web       # stop
container start api web      # start again (after a reboot or system restart)
container rm -f api web      # remove completely
Enter fullscreen mode Exit fullscreen mode

Note: containers started with --rm are deleted when stopped — re-run the
container run command instead of container start.

Optional — up/down scripts (your "compose" replacement)

up.sh:

#!/bin/bash
set -e
container run -d --name api python:3-alpine sh -c \
  'echo "{\"message\": \"hello from api\"}" > /tmp/index.html && python3 -m http.server 8000 -d /tmp'

# wait until api is answering before starting web
until container run --rm alpine wget -qO- http://api.test:8000 >/dev/null 2>&1; do
  sleep 1
done

container run -d --name web -p 8080:80 python:3-alpine sh -c \
  'pip install -q flask requests && python3 -c "
from flask import Flask; import requests
app = Flask(__name__)
@app.route(\"/\")
def home(): return \"api said: \" + requests.get(\"http://api.test:8000\").text
app.run(host=\"0.0.0.0\", port=80)"'

echo "stack up → http://localhost:8080"
Enter fullscreen mode Exit fullscreen mode

down.sh:

#!/bin/bash
container rm -f api web
echo "stack down"
Enter fullscreen mode Exit fullscreen mode

Why this works without Compose

Compose feature Apple container equivalent
Service names on a shared network --name api + DNS domain → api.test
docker compose up ./up.sh
docker compose down ./down.sh
depends_on readiness loop in the script
Port publishing -p 8080:80 (same flag)

For larger stacks, community tools like Container-Compose or socktainer (Docker-socket compatibility) can bring real compose files to Apple's runtime.

Top comments (0)