One container per role -- engine, worker, ui, notifier, sink -- sharing one log server, all in one stack. This is the shape Choose your deployment points at for Docker Compose split by role. Topologies, section 2.2, is the mechanism.

1. Prerequisites

Docker with the compose plugin (docker compose version).

2. Install

Nothing to install beyond Docker; the stack pulls the jaque image on first up. See Docker for what the image is built on and how every flag maps to a JAQUE_<FLAG> environment variable.

3. The log server

The log service in the stack below is the shared log server every role points -eventlog, -coordination and -queue at. There is nothing to run separately; docker compose up brings it up with the rest.

4. The files

The canonical copy of both files below is deploy/compose/distributed/docker-compose.yml and deploy/compose/distributed/jaque.cue in the repository. Every role but worker mounts jaque.cue read-only; engine runs -notify-delivery external because notifier is also running.

# deploy/compose/distributed/docker-compose.yml
services:
  log:
    image: nats:2.10-alpine
    command: ["-js", "-sd", "/data"]
    volumes:
      - jaque-log-data:/data

  engine:
    image: ${JAQUE_IMAGE:-jaque:latest}
    environment:
      JAQUE_TARGET: engine
      JAQUE_CONFIG: /etc/jaque/config.cue
      JAQUE_LISTEN: 0.0.0.0:8080
      JAQUE_EVENTLOG: nats://log:4222
      JAQUE_COORDINATION: nats://log:4222
      JAQUE_QUEUE: nats://log:4222
      JAQUE_NOTIFY_DELIVERY: external
    volumes:
      - ./jaque.cue:/etc/jaque/config.cue:ro
    depends_on:
      - log
    restart: unless-stopped

  worker:
    image: ${JAQUE_IMAGE:-jaque:latest}
    environment:
      JAQUE_TARGET: worker
      JAQUE_LISTEN: 0.0.0.0:8080
      JAQUE_QUEUE: nats://log:4222
      JAQUE_ZONES: default
    depends_on:
      - log
    restart: unless-stopped

  ui:
    image: ${JAQUE_IMAGE:-jaque:latest}
    environment:
      JAQUE_TARGET: ui
      JAQUE_CONFIG: /etc/jaque/config.cue
      JAQUE_LISTEN: 0.0.0.0:8080
      JAQUE_EVENTLOG: nats://log:4222
      JAQUE_COORDINATION: nats://log:4222
    volumes:
      - ./jaque.cue:/etc/jaque/config.cue:ro
    ports:
      - "8080:8080"
    depends_on:
      - log
    restart: unless-stopped

  notifier:
    image: ${JAQUE_IMAGE:-jaque:latest}
    environment:
      JAQUE_TARGET: notifier
      JAQUE_CONFIG: /etc/jaque/config.cue
      JAQUE_LISTEN: 0.0.0.0:8080
      JAQUE_EVENTLOG: nats://log:4222
      JAQUE_COORDINATION: nats://log:4222
    volumes:
      - ./jaque.cue:/etc/jaque/config.cue:ro
    depends_on:
      - log
    restart: unless-stopped

  sink:
    image: ${JAQUE_IMAGE:-jaque:latest}
    environment:
      JAQUE_TARGET: sink
      JAQUE_CONFIG: /etc/jaque/config.cue
      JAQUE_LISTEN: 0.0.0.0:8080
      JAQUE_EVENTLOG: nats://log:4222
      JAQUE_COORDINATION: nats://log:4222
    volumes:
      - ./jaque.cue:/etc/jaque/config.cue:ro
    depends_on:
      - log
    restart: unless-stopped

volumes:
  jaque-log-data: {}
// deploy/compose/distributed/jaque.cue
hosts: {
	self: {
		address: "127.0.0.1"
		check: {type: "icmp", host: "127.0.0.1"}
	}
}

Only ui publishes a port; every other role is reached over the compose network by the other services, the same way a Prometheus scrape config would reach them.

5. Start

cd deploy/compose/distributed
docker compose up -d

6. Verify

docker compose exec ui /jaque version
curl http://localhost:8080/status.json

The dashboard is at http://localhost:8080/. jaque_membership_size is 0 on ui -- it never joins engine membership -- so it has to be read from engine instead, which has no published port. Point your metrics stack at engine:8080/metrics on the compose network, the same way it would reach it in production; with one engine replica the value is 1.

7. Add a worker zone

Add a second worker service (or scale the existing one with docker compose up -d --scale worker=2) and set JAQUE_ZONES to the zone name your hosts in jaque.cue carry; a check only runs on a worker serving its object's zone. See Topologies, section 3.

8. Where things live

Config is the mounted jaque.cue, read-only, on every role but worker. State is the jaque-log-data volume, owned by the log service; no other role keeps local state. Logs go to docker compose logs <service>.

9. Upgrade

docker compose pull
docker compose up -d

Set JAQUE_IMAGE to pin a specific tag instead of latest. The event log on log's volume survives the upgrade.

10. Next steps

CUE in practice adds services and contacts to jaque.cue. Contacts and policies wires a hard state to a person, delivered by the notifier role. Cluster and coordination is what adding a second engine service buys.

11. Security considerations

Only ui's port is published; every other role stays on the compose network. Cluster and coordination, section 7, states what access to the log server's subjects amounts to -- treat that network the same as the dashboard's.