Multi-container stacks with cardinal.toml

Declare a whole stack — web server, database, cache — in one file and start it with cardinal up.

Single containers are the easy 90%. When a workload grows — a web app, its

database, a cache — you want one file that describes the whole stack and two

commands to start and stop it. That file is cardinal.toml.

1. The file

[container.web]
image = "nginx:alpine"
ports = ["80:80", "443:80"]
volumes = ["./html:/usr/share/nginx/html"]
restart = "always"

[container.db]
image = "postgres:16"
ports = ["5432:5432"]
env = { POSTGRES_PASSWORD = "secret", POSTGRES_DB = "myapp" }
volumes = ["pg_data:/var/lib/postgresql/data"]
restart = "always"

Every [container.<name>] block maps to a cardinal run with the same

fields: image, ports, volumes, env, restart, memory, cpus,

network, cap_add, and more.

2. Up and down

cardinal up                 # create/start everything
cardinal up web             # start only one service
cardinal down               # stop/remove everything from this file
cardinal down -a            # remove ALL containers, regardless of file

Containers on the same cardinal0 bridge reach each other directly — the

host gateway is 10.0.2.1, and containers get sequential IPs like

10.0.2.2, 10.0.2.3.

3. Compose YAML also works

If you already have a docker-compose.yaml, cardinal reads it as-is:

services:
  db:
    image: postgres:16
    restart: always
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: pg_isready -U myapp
      interval: 5s
      retries: 10

  api:
    image: myapp:v1
    restart: always
    ports:
      - "8000:8000"
    environment:
      DB_HOST: db
      DB_PASSWORD: secret
    depends_on:
      db:
        condition: service_healthy

volumes:
  pgdata:
cardinal up

depends_on is respected — including service_healthy, which waits for the

database's healthcheck to pass before starting the app.

4. A realistic stack: WordPress + MySQL

[container.db]
image = "mysql:8"
env = {
  MYSQL_ROOT_PASSWORD = "rootpass",
  MYSQL_DATABASE = "wordpress",
  MYSQL_USER = "wpuser",
  MYSQL_PASSWORD = "wppass",
}
volumes = ["wp_data:/var/lib/mysql"]
restart = "always"

[container.wordpress]
image = "wordpress:latest"
ports = ["8080:80"]
env = {
  WORDPRESS_DB_HOST = "db",
  WORDPRESS_DB_USER = "wpuser",
  WORDPRESS_DB_PASSWORD = "wppass",
  WORDPRESS_DB_NAME = "wordpress",
}
volumes = ["wp_uploads:/var/www/html/wp-content/uploads"]
depends_on = ["db"]
restart = "always"

Note WORDPRESS_DB_HOST = "db" — cardinal resolves the service name to the

container's bridge IP, so no hardcoded addresses.

5. Generating a stack

The Stack Builder does all of this visually: pick services from

the blueprint registry (or start from a preset), tweak names, ports, volumes

and env, and download the finished cardinal.toml:

cardinal up -f cardinal.toml