services: api: build: . container_name: open-dispatch-api restart: unless-stopped env_file: .env # Pin the data dir in the container environment: it must not be shadowed # by a stray OPEN_DISPATCH_DATA line in .env, or api and worker each get # their own queue and the ./data volume is never used. environment: - OPEN_DISPATCH_DATA=/data # SEC-1: bind to loopback only. The API runs unauthenticated when # OPEN_DISPATCH_API_TOKEN is empty, so binding all interfaces by default # exposed create/delete/dispatch to anyone who could reach the host. # To serve remotely, front it with an authenticated reverse proxy (TLS + # bearer auth) or set OPEN_DISPATCH_API_TOKEN and change this to # "8000:8000" explicitly — see README → "Exposing the API remotely". ports: - "127.0.0.1:8000:8000" volumes: - ./data:/data # No `depends_on: redis` — Redis is opt-in via REDIS_URL. worker: build: . container_name: open-dispatch-worker restart: unless-stopped env_file: .env environment: - OPEN_DISPATCH_DATA=/data volumes: - ./data:/data depends_on: api: condition: service_healthy command: ["python", "-m", "scheduler.worker"] # Optional — only used when REDIS_URL points here in .env. # Comment out if you want pure JSONL self-host. redis: image: redis:7-alpine container_name: open-dispatch-redis restart: unless-stopped profiles: ["redis"] # Not published to the host: only api and worker reach it over the # compose network, and the default redis image has no auth. volumes: - redis-data:/data command: ["redis-server", "--appendonly", "yes"] volumes: redis-data: