name: trigger

x-logging: &logging-config
  driver: ${LOGGING_DRIVER:-local}
  options:
    max-size: ${LOGGING_MAX_SIZE:-20m}
    max-file: ${LOGGING_MAX_FILES:-5}
    compress: ${LOGGING_COMPRESS:-true}

services:
  webapp:
    image: ghcr.io/triggerdotdev/trigger.dev:${TRIGGER_IMAGE_TAG:-latest}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    ports:
      - ${WEBAPP_PUBLISH_IP:-0.0.0.0}:8030:3000
    depends_on:
      - postgres
      - redis
      - clickhouse
      - s2
    networks:
      - webapp
      - supervisor
    volumes:
      - shared:/home/node/shared
    # Only needed for bootstrap
    user: root
    # Only needed for bootstrap
    command: sh -c "chown -R node:node /home/node/shared && exec ./scripts/entrypoint.sh"
    healthcheck:
      test:
        [
          "CMD",
          "node",
          "-e",
          "http.get('http://localhost:3000/healthcheck', res => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
        ]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 10s
    environment:
      APP_ORIGIN: ${APP_ORIGIN:-http://localhost:8030}
      LOGIN_ORIGIN: ${LOGIN_ORIGIN:-http://localhost:8030}
      API_ORIGIN: ${API_ORIGIN:-http://localhost:8030}
      ELECTRIC_ORIGIN: http://electric:3000
      # Realtime streams v2, backed by the bundled s2-lite service. This powers
      # AI-agent token streaming and run streams. Point the endpoint at a hosted
      # S2 (https://s2.dev) instead by overriding these and setting an access token.
      # To fall back to the Redis-backed v1 streams, set the version to `v1`.
      REALTIME_STREAMS_DEFAULT_VERSION: ${REALTIME_STREAMS_DEFAULT_VERSION:-v2}
      REALTIME_STREAMS_S2_BASIN: ${REALTIME_STREAMS_S2_BASIN:-trigger-realtime}
      REALTIME_STREAMS_S2_ENDPOINT: ${REALTIME_STREAMS_S2_ENDPOINT:-http://s2/v1}
      REALTIME_STREAMS_S2_SKIP_ACCESS_TOKENS: ${REALTIME_STREAMS_S2_SKIP_ACCESS_TOKENS:-true}
      REALTIME_STREAMS_S2_ACCESS_TOKEN: ${REALTIME_STREAMS_S2_ACCESS_TOKEN:-}
      DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
      DIRECT_URL: ${DIRECT_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
      SESSION_SECRET: ${SESSION_SECRET}
      MAGIC_LINK_SECRET: ${MAGIC_LINK_SECRET}
      ENCRYPTION_KEY: ${ENCRYPTION_KEY}
      MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET}
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_TLS_DISABLED: true
      APP_LOG_LEVEL: info
      DEV_OTEL_EXPORTER_OTLP_ENDPOINT: ${DEV_OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:8030/otel}
      DEPLOY_REGISTRY_HOST: ${DOCKER_REGISTRY_URL:-localhost:5000}
      DEPLOY_REGISTRY_NAMESPACE: ${DOCKER_REGISTRY_NAMESPACE:-trigger}
      OBJECT_STORE_BASE_URL: ${OBJECT_STORE_BASE_URL:-http://minio:9000}
      OBJECT_STORE_ACCESS_KEY_ID: ${OBJECT_STORE_ACCESS_KEY_ID}
      OBJECT_STORE_SECRET_ACCESS_KEY: ${OBJECT_STORE_SECRET_ACCESS_KEY}
      GRACEFUL_SHUTDOWN_TIMEOUT: 1000
      NODE_MAX_OLD_SPACE_SIZE: ${NODE_MAX_OLD_SPACE_SIZE}
      # Bootstrap - this will automatically set up a worker group for you
      # This will NOT work for split deployments
      TRIGGER_BOOTSTRAP_ENABLED: 1
      TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: bootstrap
      TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: /home/node/shared/worker_token
      # ClickHouse configuration
      CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://default:password@clickhouse:8123?secure=false}
      CLICKHOUSE_LOG_LEVEL: ${CLICKHOUSE_LOG_LEVEL:-info}
      # Run replication
      RUN_REPLICATION_ENABLED: ${RUN_REPLICATION_ENABLED:-1}
      RUN_REPLICATION_CLICKHOUSE_URL: ${RUN_REPLICATION_CLICKHOUSE_URL:-http://default:password@clickhouse:8123}
      RUN_REPLICATION_LOG_LEVEL: ${RUN_REPLICATION_LOG_LEVEL:-info}
      # Limits
      # TASK_PAYLOAD_OFFLOAD_THRESHOLD: 524288 # 512KB
      # TASK_PAYLOAD_MAXIMUM_SIZE: 3145728 # 3MB
      # BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: 1000000 # 1MB
      # TASK_RUN_METADATA_MAXIMUM_SIZE: 262144 # 256KB
      # DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: 100
      # DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: 100
      # Internal OTEL configuration
      INTERNAL_OTEL_TRACE_LOGGING_ENABLED: ${INTERNAL_OTEL_TRACE_LOGGING_ENABLED:-0}

  postgres:
    image: postgres:${POSTGRES_IMAGE_TAG:-14}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    ports:
      - ${POSTGRES_PUBLISH_IP:-127.0.0.1}:5433:5432
    volumes:
      - postgres:/var/lib/postgresql/data/
    networks:
      - webapp
    command:
      - -c
      - wal_level=logical
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
      POSTGRES_DB: ${POSTGRES_DB:-postgres}
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  redis:
    image: redis:${REDIS_IMAGE_TAG:-7}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    ports:
      - ${REDIS_PUBLISH_IP:-127.0.0.1}:6389:6379
    volumes:
      - redis:/data
    networks:
      - webapp
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  electric:
    image: electricsql/electric:${ELECTRIC_IMAGE_TAG:-1.2.4}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    depends_on:
      - postgres
    networks:
      - webapp
    environment:
      DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
      ELECTRIC_INSECURE: true
      ELECTRIC_USAGE_REPORTING: false
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/v1/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  clickhouse:
    image: bitnamilegacy/clickhouse:${CLICKHOUSE_IMAGE_TAG:-latest}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    ports:
      - ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9123:8123
      - ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9090:9000
    environment:
      CLICKHOUSE_ADMIN_USER: ${CLICKHOUSE_USER:-default}
      CLICKHOUSE_ADMIN_PASSWORD: ${CLICKHOUSE_PASSWORD:-password}
    volumes:
      - clickhouse:/bitnami/clickhouse
      - ../clickhouse/override.xml:/bitnami/clickhouse/etc/config.d/override.xml:ro
    networks:
      - webapp
    healthcheck:
      test:
        [
          "CMD",
          "clickhouse-client",
          "--host",
          "localhost",
          "--port",
          "9000",
          "--user",
          "${CLICKHOUSE_USER:-default}",
          "--password",
          "${CLICKHOUSE_PASSWORD:-password}",
          "--query",
          "SELECT 1",
        ]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

  registry:
    image: registry:${REGISTRY_IMAGE_TAG:-2}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    ports:
      - ${REGISTRY_PUBLISH_IP:-127.0.0.1}:5000:5000
    networks:
      - webapp
    volumes:
      # registry-user:very-secure-indeed
      - ../registry/auth.htpasswd:/auth/htpasswd:ro
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:5000/"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  minio:
    image: bitnamilegacy/minio:${MINIO_IMAGE_TAG:-latest}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    ports:
      - ${MINIO_PUBLISH_IP:-127.0.0.1}:9000:9000
      - ${MINIO_PUBLISH_IP:-127.0.0.1}:9001:9001
    networks:
      - webapp
    volumes:
      - minio:/bitnami/minio/data
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER:-admin}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-very-safe-password}
      MINIO_DEFAULT_BUCKETS: packets
      MINIO_BROWSER: "on"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 5s
      timeout: 10s
      retries: 5
      start_period: 10s

  # Generates the s2-lite basin spec from REALTIME_STREAMS_S2_BASIN so the bundled
  # basin always matches the basin the webapp targets. s2-lite is distroless (no
  # shell), so a tiny init container writes the spec into a shared volume first.
  s2-init:
    image: busybox:${BUSYBOX_IMAGE_TAG:-1.37}
    restart: "no"
    logging: *logging-config
    environment:
      REALTIME_STREAMS_S2_BASIN: ${REALTIME_STREAMS_S2_BASIN:-trigger-realtime}
    command:
      - sh
      - -c
      - |
        cat > /config/s2-spec.json <<JSON
        {
          "basins": [
            {
              "name": "$$REALTIME_STREAMS_S2_BASIN",
              "config": { "create_stream_on_append": true, "create_stream_on_read": true }
            }
          ]
        }
        JSON
    volumes:
      - s2-config:/config
    networks:
      - webapp

  # s2-lite: the open-source, self-hostable S2 (https://s2.dev) server that backs
  # realtime streams v2. It's distroless (no shell), so a wget/curl healthcheck
  # always reports unhealthy even when the API is up — hence no healthcheck.
  # Runs as root so it can write to the named volume; storage lives in SlateDB
  # under /data, and the basin is created on startup from the generated spec.
  s2:
    image: ${S2_IMAGE:-ghcr.io/s2-streamstore/s2:latest@sha256:d6ded5ca7dd619fa7c946f06e39a98f9c95c6883c8bb884e5eaa129f232c920c}
    restart: ${RESTART_POLICY:-unless-stopped}
    logging: *logging-config
    depends_on:
      s2-init:
        condition: service_completed_successfully
    command: ["lite", "--init-file", "/config/s2-spec.json", "--local-root", "/data"]
    user: "0:0"
    volumes:
      - s2-config:/config
      - s2:/data
    networks:
      - webapp

volumes:
  clickhouse:
  postgres:
  redis:
  shared:
  minio:
  s2:
  s2-config:

networks:
  docker-proxy:
    name: docker-proxy
  supervisor:
    name: supervisor
  webapp:
    name: webapp
