name: playbook-runner services: ansible: build: context: . dockerfile: apps/ansible/Dockerfile container_name: ansible init: true env_file: - path: apps/ansible/.env required: false volumes: - ansible_state:/app/state # Internal-only. Use compose.debug.yml to publish 8000 on the host. expose: - "8000" - "50051" healthcheck: # HTTP health plus a TCP connect to gRPC: the backend only talks gRPC. test: - CMD - python3 - -c - "import socket, urllib.request; urllib.request.urlopen('http://localhost:8000/api/health', timeout=3).read(); socket.create_connection(('localhost', 50051), 2).close()" interval: 10s timeout: 5s retries: 5 start_period: 10s restart: unless-stopped frontend: build: context: . dockerfile: apps/frontend/Dockerfile container_name: frontend init: true # Reached only through the gateway, which publishes the site expose: - "4321" env_file: - path: apps/frontend/.env required: false environment: BETTER_AUTH_URL: http://backend:3000 depends_on: backend: condition: service_healthy healthcheck: test: [ "CMD", "node", "-e", "fetch('http://localhost:4321/login').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" ] interval: 10s timeout: 5s retries: 5 start_period: 10s restart: unless-stopped backend: build: context: . dockerfile: apps/backend/Dockerfile container_name: backend init: true expose: - "3000" env_file: - path: apps/backend/.env required: false environment: CORS_ORIGIN: http://localhost:4321 # The gateway's gRPC router forwards package `run` to ansible:50051 ANSIBLE_GRPC_TARGET: gateway:50050 depends_on: ansible: condition: service_healthy gateway: condition: service_healthy healthcheck: test: [ "CMD", "bun", "-e", "fetch('http://localhost:3000/').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" ] interval: 10s timeout: 5s retries: 5 start_period: 10s restart: unless-stopped # Gateway (apps/gateway/Caddyfile): the public entry point on :80, routing # /rpc, /api, /scalar and /openapi.json to the backend and the rest to the # frontend, and the internal gRPC router on :50050 that the backend dials # (ANSIBLE_GRPC_TARGET). No route reaches the Ansible runner from outside. # No depends_on: the backend waits for it, so it must come up first. gateway: build: context: . dockerfile: apps/gateway/Dockerfile container_name: gateway ports: - "${GATEWAY_PORT:-${FRONTEND_PORT:-4321}}:80" expose: - "50050" healthcheck: test: [ "CMD", "wget", "-q", "--spider", "http://localhost:50050/health" ] interval: 10s timeout: 5s retries: 5 start_period: 5s restart: unless-stopped volumes: ansible_state: