# ORCH-103 (Type B Bundled, ADR-001 D1–D4): самодостаточный compose ВСЕГО стека # для тиража «под ключ» на хост заказчика: orchestrator + orchestrator-watchdog + # Gitea + Plane CE (зеркало официального selfhost-référence makeplane/plane # v0.23.1: имена сервисов и env-контракт — upstream, анти-дрейф к их докам; наши # отличия от référence: пиннинг неподвижными тегами литералом вместо ${APP_RELEASE} # (NFR-6, держится tests/test_bundle_compose.py), убраны replicas/platform/SENTRY, # секреты БЕЗ дефолтных значений — их генерирует scripts/bootstrap_bundle.py). # # Этот файл НЕ исполняется в нашем прод-контуре (корневой docker-compose.yml — # байт-в-байт, заморожен анти-дрейфом ORCH-102); активация — только явный запуск # оператором на целевом хосте (паттерн ORCH-009, kill-switch не нужен). # # Конфиг-слои (D2): интерполяции ${VAR} читаются compose'ом из deploy/bundled/.env # (авто-чтение из project dir — без --env-file-футгана); канон ключей — # deploy/bundled/.env.example (key-set-sync держит тест). Runtime-конфиг орка и # watchdog — КОРНЕВЫЕ .env / .env.watchdog (канон Lite 1:1, REPLICATION §2); # их единственный писатель — bootstrap_bundle.py. # # Сеть (D4): одна bridge-сеть проекта; машинный трафик — строго сервис-DNS # (Plane→орк http://orchestrator:8500/webhook/plane, Gitea→орк .../webhook/gitea, # орк→Plane http://proxy, орк→Gitea http://gitea:3000); network_mode: host НЕ # используется (ssh-деплой-контур нашего хоста в bundle структурно спит — # ORCH_DEPLOY_SSH_HOST пуст). Наружу публикуются ТОЛЬКО человеческие порты # (орк/Plane proxy/Gitea web); postgres/redis/mq/minio не публикуются. # # Project name = узнаваемый префикс томов/контейнеров orchestrator-bundle_* (D1); # container_name сознательно НЕ пиннится ни у кого — bundle и Lite/корневой # compose не сталкиваются по именам на одном хосте. name: orchestrator-bundle networks: default: name: orchestrator-bundle driver: bridge # Env-контракт Plane CE — upstream-имена (référence v0.23.1). Значения секретов # (POSTGRES_PASSWORD/SECRET_KEY/RABBITMQ_DEFAULT_PASS/MINIO_ROOT_PASSWORD) живут # ТОЛЬКО в deploy/bundled/.env (генерирует bootstrap); дефолтных паролей нет. x-plane-env: &plane-env environment: - WEB_URL=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_PLANE_PORT:-8080} - DEBUG=0 - CORS_ALLOWED_ORIGINS=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_PLANE_PORT:-8080} - GUNICORN_WORKERS=1 # db (upstream-имена; host/port — фиксированные сервис-DNS этого файла) - PGHOST=plane-db - PGDATABASE=${POSTGRES_DB:-plane} - POSTGRES_USER=${POSTGRES_USER:-plane} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB:-plane} - POSTGRES_PORT=5432 - PGDATA=/var/lib/postgresql/data - DATABASE_URL=postgresql://${POSTGRES_USER:-plane}:${POSTGRES_PASSWORD}@plane-db:5432/${POSTGRES_DB:-plane} # redis - REDIS_HOST=plane-redis - REDIS_PORT=6379 - REDIS_URL=redis://plane-redis:6379/ # rabbitmq - RABBITMQ_HOST=plane-mq - RABBITMQ_PORT=5672 - RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER:-plane} - RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS} - RABBITMQ_DEFAULT_VHOST=${RABBITMQ_DEFAULT_VHOST:-plane} - RABBITMQ_VHOST=${RABBITMQ_DEFAULT_VHOST:-plane} - AMQP_URL=amqp://${RABBITMQ_DEFAULT_USER:-plane}:${RABBITMQ_DEFAULT_PASS}@plane-mq:5672/${RABBITMQ_DEFAULT_VHOST:-plane} # application secret (генерирует bootstrap; дефолта сознательно НЕТ) - SECRET_KEY=${SECRET_KEY} # datastore (minio) - USE_MINIO=1 - AWS_REGION= - AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER:-plane-minio-admin} - AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD} - AWS_S3_ENDPOINT_URL=http://plane-minio:9000 - AWS_S3_BUCKET_NAME=uploads - MINIO_ROOT_USER=${MINIO_ROOT_USER:-plane-minio-admin} - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} - BUCKET_NAME=uploads - FILE_SIZE_LIMIT=5242880 # live server - API_BASE_URL=http://api:8000 # proxy - NGINX_PORT=80 services: # ── Платформа: орк + sidecar-watchdog (образы собираются из этого же чекаута; # корневой Dockerfile / watchdog/Dockerfile — без правок, NFR-1) ────────── orchestrator: build: context: ../.. # ORCH-101 (D5): uid/gid/home двигаются ОДНОЙ группой с user: и таргетами # маунтов ниже (инвариант ORCH-040). Дефолты bundle нейтральны (D2). args: APP_UID: ${ORCH_RUN_UID:-1000} APP_GID: ${ORCH_RUN_GID:-1000} APP_HOME: ${ORCH_AGENT_HOME_DIR:-/home/orchestrator} restart: unless-stopped user: "${ORCH_RUN_UID:-1000}:${ORCH_RUN_GID:-1000}" init: true command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8500"] ports: # человеческая точка: операторский smoke `curl /health` (D4) - "${BUNDLE_ORCH_PORT:-8500}:8500" volumes: # данные/репозитории — bind ВНУТРИ project dir (uid-причины ORCH-040; # покрыты .gitignore: неякорный data/ + deploy/bundled/repos/) - ./data:/app/data - ./repos:/repos - /var/run/docker.sock:/var/run/docker.sock # LLM-предусловие хоста заказчика (bundle его НЕ поставляет, BRD §1.3) - ${ORCH_HOST_CLAUDE_CODE_DIR:-/usr/lib/node_modules/@anthropic-ai/claude-code}:/opt/claude-code:ro - ${ORCH_HOST_NODE_BIN:-/usr/bin/node}:/usr/bin/node:ro - ${ORCH_HOST_CLAUDE_DIR:-~/.claude}:${ORCH_AGENT_HOME_DIR:-/home/orchestrator}/.claude - ${ORCH_HOST_CLAUDE_JSON:-~/.claude.json}:${ORCH_AGENT_HOME_DIR:-/home/orchestrator}/.claude.json:ro # ssh-контур в bundle сознательно НЕ вводится (ADR D8): git-доступ агентов # — HTTP token-remote, деплой-хуки нашего хоста структурно спят. # runtime-конфиг орка собирает bootstrap (шаг 8); required:false — первый # `up -d` поднимает стек ДО сборки конфига (AC-1), орк жив без него. env_file: - path: ../../.env required: false environment: - ORCH_REPOS_DIR=/repos group_add: - "${ORCH_DOCKER_GID:-999}" orchestrator-watchdog: build: context: ../.. dockerfile: watchdog/Dockerfile restart: unless-stopped init: true mem_limit: 128m mem_reservation: 32m volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./repos:/repos:ro - ./data:/app/data:ro env_file: - path: ../../.env.watchdog required: false environment: # bundle-сеть ≠ host-network Lite: метрики — по сервис-DNS; имя контейнера # орка детерминировано project name (container_name не пиннится, D1). # environment перекрывает env_file → когерентность механическая (TR-8). - WATCHDOG_METRICS_URL=http://orchestrator:8500/metrics - WATCHDOG_CONTAINERS=orchestrator-bundle-orchestrator-1 group_add: - "${ORCH_DOCKER_GID:-999}" # ── Gitea (D6): официальный образ, НЕ rootless; init полностью автоматом — # bootstrap создаёт админа/токен через `gitea admin ...` CLI в контейнере. # Branch protection на main НЕ настраивается (норматив D10 ORCH-009/INV-4). gitea: image: gitea/gitea:1.22.6 restart: unless-stopped ports: - "${BUNDLE_GITEA_HTTP_PORT:-3000}:3000" environment: - GITEA__database__DB_TYPE=sqlite3 - GITEA__security__INSTALL_LOCK=true - GITEA__server__DOMAIN=${BUNDLE_PUBLIC_HOST:-localhost} - GITEA__server__ROOT_URL=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_GITEA_HTTP_PORT:-3000}/ # ssh-контур не вводится (D8): порт не публикуется, ssh выключен. - GITEA__server__DISABLE_SSH=true - GITEA__service__DISABLE_REGISTRATION=true # МИНА TR-4 (D4): Gitea по умолчанию режет webhook'и в приватные адреса — # без этой строки «задача не появилась» гарантирован. - GITEA__webhook__ALLOWED_HOST_LIST=orchestrator volumes: - gitea-data:/data healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"] interval: 10s timeout: 5s retries: 12 # ── Plane CE — зеркало upstream selfhost-référence v0.23.1 (D3) ──────────── web: <<: *plane-env image: makeplane/plane-frontend:v0.23.1 restart: unless-stopped command: node web/server.js web depends_on: - api - worker space: <<: *plane-env image: makeplane/plane-space:v0.23.1 restart: unless-stopped command: node space/server.js space depends_on: - api - worker - web admin: <<: *plane-env image: makeplane/plane-admin:v0.23.1 restart: unless-stopped command: node admin/server.js admin depends_on: - api - web live: <<: *plane-env image: makeplane/plane-live:v0.23.1 restart: unless-stopped command: node live/dist/server.js live depends_on: - api - web api: <<: *plane-env image: makeplane/plane-backend:v0.23.1 restart: unless-stopped command: ./bin/docker-entrypoint-api.sh volumes: - logs_api:/code/plane/logs depends_on: - plane-db - plane-redis - plane-mq worker: <<: *plane-env image: makeplane/plane-backend:v0.23.1 restart: unless-stopped command: ./bin/docker-entrypoint-worker.sh volumes: - logs_worker:/code/plane/logs depends_on: - api - plane-db - plane-redis - plane-mq beat-worker: <<: *plane-env image: makeplane/plane-backend:v0.23.1 restart: unless-stopped command: ./bin/docker-entrypoint-beat.sh volumes: - logs_beat-worker:/code/plane/logs depends_on: - api - plane-db - plane-redis - plane-mq migrator: <<: *plane-env image: makeplane/plane-backend:v0.23.1 restart: "no" command: ./bin/docker-entrypoint-migrator.sh volumes: - logs_migrator:/code/plane/logs depends_on: - plane-db - plane-redis plane-db: <<: *plane-env image: postgres:15.7-alpine restart: unless-stopped command: postgres -c 'max_connections=1000' volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 12 plane-redis: <<: *plane-env image: valkey/valkey:7.2.5-alpine restart: unless-stopped volumes: - redisdata:/data healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 5s retries: 12 plane-mq: <<: *plane-env image: rabbitmq:3.13.6-management-alpine restart: always volumes: - rabbitmq_data:/var/lib/rabbitmq healthcheck: test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] interval: 15s timeout: 10s retries: 12 plane-minio: <<: *plane-env # upstream-référence держит latest — bundle пиннит неподвижный тег (NFR-6) image: minio/minio:RELEASE.2024-05-28T17-19-04Z restart: unless-stopped command: server /export --console-address ":9090" volumes: - uploads:/export healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:9000/minio/health/live"] interval: 10s timeout: 5s retries: 12 proxy: <<: *plane-env image: makeplane/plane-proxy:v0.23.1 restart: unless-stopped ports: # человеческая точка: UI Plane в браузере оператора (D4) - "${BUNDLE_PLANE_PORT:-8080}:80" depends_on: - web - api - space # Состояние Plane/Gitea — именованные тома проекта (префикс orchestrator-bundle_, # D1/D2); preflight bootstrap детектирует «грязный хост» по этому префиксу. volumes: pgdata: redisdata: uploads: logs_api: logs_worker: logs_beat-worker: logs_migrator: rabbitmq_data: gitea-data: