DealDocumentScreening/docker-compose.yml
2026-08-22 18:30:52 +03:00

482 lines
19 KiB
YAML

# «Контракт-чек» — infrastructure (Step 1).
#
# Default (`docker compose up`) starts ONLY infra: postgres + redis + rabbitmq
# + minio (+ minio-init). Service containers (api, worker-extract, worker-analyze,
# bot) and the observability/edge stacks are added behind profiles in later steps
# (docs/ARCHITECTURE.md §20).
#
# Durability posture (§10): quorum-ready. Postgres is configured
# wal_level=replica + WAL archiving (replica/PITR-ready). RabbitMQ quorum queues
# are declared by the app (core/mq/topology.py) — they replicate the moment a
# 3-node cluster is added. Named volumes everywhere; restart: unless-stopped.
services:
postgres:
image: postgres:18-alpine
container_name: contract_check-postgres
restart: unless-stopped
command:
- "postgres"
- "-c"
- "wal_level=replica"
- "-c"
- "archive_mode=on"
- "-c"
- "archive_command=test ! -f /walarchive/%f && cp %p /walarchive/%f"
environment:
POSTGRES_USER: ${POSTGRES_USER:-contract_check}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-contract_check}
POSTGRES_DB: ${POSTGRES_DB:-contract_check}
volumes:
- pgdata:/var/lib/postgresql
- pgwal:/walarchive
ports:
- "15432:5432"
healthcheck:
test:
- CMD-SHELL
- "pg_isready -U ${POSTGRES_USER:-contract_check} -d ${POSTGRES_DB:-contract_check}"
interval: 5s
timeout: 3s
retries: 10
redis:
image: redis:8-alpine
container_name: contract_check-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redisdata:/data
ports:
- "17379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
rabbitmq:
image: rabbitmq:4-management-alpine
container_name: contract_check-rabbitmq
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-contract_check}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS:-contract_check}
RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_VHOST:-/}
volumes:
- rabbitmq:/var/lib/rabbitmq
ports:
- "5672:5672" # AMQP
- "15672:15672" # management UI (http://localhost:15672)
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 10
start_period: 15s
minio:
image: minio/minio:latest
container_name: contract_check-minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY:-contract_check}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:-contract_check}
volumes:
- minio:/data
ports:
- "9000:9000" # S3 API
- "9001:9001" # console (http://localhost:9001)
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:9000/minio/health/ready"]
interval: 10s
timeout: 5s
retries: 10
start_period: 10s
# One-shot: create the bucket + an ILM expiry rule (152-ФЗ retention lever).
# Service containers (Step 2+) gate on `service_completed_successfully`.
minio-init:
image: minio/mc:latest
container_name: contract_check-minio-init
depends_on:
minio:
condition: service_healthy
entrypoint: /bin/sh
command:
- -c
- |
set -e
mc alias set local http://minio:9000 "$${MINIO_ROOT_USER:-contract_check}" "$${MINIO_ROOT_PASSWORD:-contract_check}"
mc mb --ignore-existing local/${S3_BUCKET:-contract-check-docs}
mc anonymous set none local/${S3_BUCKET:-contract-check-docs} || true
# Expire raw docs + extracted text after DOC_RETENTION_DAYS (default 7).
mc ilm rule add --expire-days ${DOC_RETENTION_DAYS:-7} local/${S3_BUCKET:-contract-check-docs} || true
echo "bucket ${S3_BUCKET:-contract-check-docs} ready (ilm expire ${DOC_RETENTION_DAYS:-7}d)"
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY:-contract_check}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:-contract_check}
restart: "no"
# ── SERVICES (profile: services) ────────────────────────────────────────────
# Core FastAPI service. Runs migrations separately (see deploy docs); assumes
# the DB is migrated before accepting traffic via healthcheck delay.
api:
profiles: ["services"]
build:
context: .
dockerfile: srv/api/Dockerfile
container_name: contract_check-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio-init:
condition: service_completed_successfully
environment:
ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-contract_check}:${POSTGRES_PASSWORD:-contract_check}@postgres:5432/${POSTGRES_DB:-contract_check}
REDIS_URL: redis://redis:6379/0
RABBITMQ_URL: amqp://${RABBITMQ_USER:-contract_check}:${RABBITMQ_PASS:-contract_check}@rabbitmq:5672/${RABBITMQ_VHOST:-/}
S3_ENDPOINT_URL: http://minio:9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-contract_check}
S3_SECRET_KEY: ${S3_SECRET_KEY:-contract_check}
S3_BUCKET: ${S3_BUCKET:-contract-check-docs}
S3_REGION: ${S3_REGION:-us-east-1}
S3_SERVER_SIDE_ENCRYPTION: ${S3_SERVER_SIDE_ENCRYPTION:-false}
DOC_RETENTION_DAYS: ${DOC_RETENTION_DAYS:-7}
REFUND_POLICY: ${REFUND_POLICY:-all}
SENTRY_DSN: ${SENTRY_DSN:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: api
LLM_PROVIDER: ${LLM_PROVIDER:-ollama_cloud}
OLLAMA_HOST: ${OLLAMA_HOST:-}
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:14b}
OLLAMA_FALLBACK_MODEL: ${OLLAMA_FALLBACK_MODEL:-qwen2.5:7b}
OLLAMA_TEMPERATURE: ${OLLAMA_TEMPERATURE:-0.2}
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-3072}
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-120}
OLLAMA_MAX_CONCURRENCY: ${OLLAMA_MAX_CONCURRENCY:-3}
YANDEXGPT_API_KEY: ${YANDEXGPT_API_KEY:-}
YANDEXGPT_FOLDER_ID: ${YANDEXGPT_FOLDER_ID:-}
YANDEXGPT_MODEL: ${YANDEXGPT_MODEL:-yandexgpt-lite}
YANDEXGPT_FALLBACK_MODEL: ${YANDEXGPT_FALLBACK_MODEL:-}
YANDEXGPT_BASE_URL: ${YANDEXGPT_BASE_URL:-https://llm.api.cloud.yandex.net}
YANDEXGPT_COMPLETION_PATH: ${YANDEXGPT_COMPLETION_PATH:-/foundationModels/v1/completion}
YANDEXGPT_TEMPERATURE: ${YANDEXGPT_TEMPERATURE:-0.2}
YANDEXGPT_MAX_TOKENS: ${YANDEXGPT_MAX_TOKENS:-3072}
YANDEXGPT_TIMEOUT: ${YANDEXGPT_TIMEOUT:-120}
YANDEXGPT_MAX_CONCURRENCY: ${YANDEXGPT_MAX_CONCURRENCY:-3}
CHUNK_SIZE_CHARS: ${CHUNK_SIZE_CHARS:-10000}
API_HOST: ${API_HOST:-0.0.0.0}
API_PORT: ${API_PORT:-8000}
API_METRICS_PORT: ${API_METRICS_PORT:-9100}
CORS_ORIGINS: ${CORS_ORIGINS:-}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
JWT_SECRET: ${JWT_SECRET}
JWT_ALGORITHM: ${JWT_ALGORITHM:-HS256}
JWT_ACCESS_TTL_MINUTES: ${JWT_ACCESS_TTL_MINUTES:-1440}
JWT_REFRESH_TTL_DAYS: ${JWT_REFRESH_TTL_DAYS:-30}
WEB_AUTH_ENABLED: ${WEB_AUTH_ENABLED:-true}
WEB_ADMIN_ENABLED: ${WEB_ADMIN_ENABLED:-true}
ADMIN_REQUIRED_ROLE: ${ADMIN_REQUIRED_ROLE:-admin}
ADMIN_DEFAULT_EMAIL: ${ADMIN_DEFAULT_EMAIL:-admin@contract-check.local}
ADMIN_DEFAULT_PASSWORD: ${ADMIN_DEFAULT_PASSWORD:-}
PASSWORD_RESET_TTL_MINUTES: ${PASSWORD_RESET_TTL_MINUTES:-60}
PASSWORD_MIN_LENGTH: ${PASSWORD_MIN_LENGTH:-8}
WEB_APP_BASE_URL: ${WEB_APP_BASE_URL:-http://localhost:5173}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-no-reply@contract-check.local}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
ports:
- "${API_PORT:-8000}:8000"
- "${API_METRICS_PORT:-9100}:9100"
healthcheck:
test:
- CMD-SHELL
- "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')\""
interval: 10s
timeout: 3s
retries: 10
start_period: 15s
worker-extract:
profiles: ["services"]
build:
context: .
dockerfile: srv/worker-extract/Dockerfile
container_name: contract_check-worker-extract
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio-init:
condition: service_completed_successfully
environment:
ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-contract_check}:${POSTGRES_PASSWORD:-contract_check}@postgres:5432/${POSTGRES_DB:-contract_check}
RABBITMQ_URL: amqp://${RABBITMQ_USER:-contract_check}:${RABBITMQ_PASS:-contract_check}@rabbitmq:5672/${RABBITMQ_VHOST:-/}
S3_ENDPOINT_URL: http://minio:9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-contract_check}
S3_SECRET_KEY: ${S3_SECRET_KEY:-contract_check}
S3_BUCKET: ${S3_BUCKET:-contract-check-docs}
S3_REGION: ${S3_REGION:-us-east-1}
S3_SERVER_SIDE_ENCRYPTION: ${S3_SERVER_SIDE_ENCRYPTION:-false}
DOC_RETENTION_DAYS: ${DOC_RETENTION_DAYS:-7}
REFUND_POLICY: ${REFUND_POLICY:-all}
SENTRY_DSN: ${SENTRY_DSN:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: worker-extract
MQ_PREFETCH_EXTRACT: ${MQ_PREFETCH_EXTRACT:-1}
MQ_MAX_ATTEMPTS: ${MQ_MAX_ATTEMPTS:-5}
MQ_RETRY_BASE_MS: ${MQ_RETRY_BASE_MS:-2000}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
JWT_SECRET: ${JWT_SECRET}
JWT_ALGORITHM: ${JWT_ALGORITHM:-HS256}
JWT_ACCESS_TTL_MINUTES: ${JWT_ACCESS_TTL_MINUTES:-1440}
ports:
- "9101:9101"
worker-analyze:
profiles: ["services"]
build:
context: .
dockerfile: srv/worker-analyze/Dockerfile
container_name: contract_check-worker-analyze
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio-init:
condition: service_completed_successfully
environment:
ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-contract_check}:${POSTGRES_PASSWORD:-contract_check}@postgres:5432/${POSTGRES_DB:-contract_check}
RABBITMQ_URL: amqp://${RABBITMQ_USER:-contract_check}:${RABBITMQ_PASS:-contract_check}@rabbitmq:5672/${RABBITMQ_VHOST:-/}
S3_ENDPOINT_URL: http://minio:9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-contract_check}
S3_SECRET_KEY: ${S3_SECRET_KEY:-contract_check}
S3_BUCKET: ${S3_BUCKET:-contract-check-docs}
S3_REGION: ${S3_REGION:-us-east-1}
REFUND_POLICY: ${REFUND_POLICY:-all}
SENTRY_DSN: ${SENTRY_DSN:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: worker-analyze
MQ_PREFETCH_ANALYZE: ${MQ_PREFETCH_ANALYZE:-3}
MQ_MAX_ATTEMPTS: ${MQ_MAX_ATTEMPTS:-5}
MQ_RETRY_BASE_MS: ${MQ_RETRY_BASE_MS:-2000}
LLM_PROVIDER: ${LLM_PROVIDER:-ollama_cloud}
OLLAMA_HOST: ${OLLAMA_HOST:-}
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:14b}
OLLAMA_FALLBACK_MODEL: ${OLLAMA_FALLBACK_MODEL:-qwen2.5:7b}
OLLAMA_TEMPERATURE: ${OLLAMA_TEMPERATURE:-0.2}
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-3072}
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-120}
OLLAMA_MAX_CONCURRENCY: ${OLLAMA_MAX_CONCURRENCY:-3}
YANDEXGPT_API_KEY: ${YANDEXGPT_API_KEY:-}
YANDEXGPT_FOLDER_ID: ${YANDEXGPT_FOLDER_ID:-}
YANDEXGPT_MODEL: ${YANDEXGPT_MODEL:-yandexgpt-lite}
YANDEXGPT_FALLBACK_MODEL: ${YANDEXGPT_FALLBACK_MODEL:-}
YANDEXGPT_BASE_URL: ${YANDEXGPT_BASE_URL:-https://llm.api.cloud.yandex.net}
YANDEXGPT_COMPLETION_PATH: ${YANDEXGPT_COMPLETION_PATH:-/foundationModels/v1/completion}
YANDEXGPT_TEMPERATURE: ${YANDEXGPT_TEMPERATURE:-0.2}
YANDEXGPT_MAX_TOKENS: ${YANDEXGPT_MAX_TOKENS:-3072}
YANDEXGPT_TIMEOUT: ${YANDEXGPT_TIMEOUT:-120}
YANDEXGPT_MAX_CONCURRENCY: ${YANDEXGPT_MAX_CONCURRENCY:-3}
CHUNK_SIZE_CHARS: ${CHUNK_SIZE_CHARS:-10000}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
JWT_SECRET: ${JWT_SECRET}
JWT_ALGORITHM: ${JWT_ALGORITHM:-HS256}
JWT_ACCESS_TTL_MINUTES: ${JWT_ACCESS_TTL_MINUTES:-1440}
ports:
- "9102:9102"
worker-prescreen:
profiles: ["services"]
build:
context: .
dockerfile: srv/worker-prescreen/Dockerfile
container_name: contract_check-worker-prescreen
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio-init:
condition: service_completed_successfully
environment:
ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-contract_check}:${POSTGRES_PASSWORD:-contract_check}@postgres:5432/${POSTGRES_DB:-contract_check}
RABBITMQ_URL: amqp://${RABBITMQ_USER:-contract_check}:${RABBITMQ_PASS:-contract_check}@rabbitmq:5672/${RABBITMQ_VHOST:-/}
S3_ENDPOINT_URL: http://minio:9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-contract_check}
S3_SECRET_KEY: ${S3_SECRET_KEY:-contract_check}
S3_BUCKET: ${S3_BUCKET:-contract-check-docs}
S3_REGION: ${S3_REGION:-us-east-1}
REFUND_POLICY: ${REFUND_POLICY:-all}
SENTRY_DSN: ${SENTRY_DSN:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: worker-prescreen
MQ_PREFETCH_PRESCREEN: ${MQ_PREFETCH_PRESCREEN:-1}
MQ_MAX_ATTEMPTS: ${MQ_MAX_ATTEMPTS:-5}
MQ_RETRY_BASE_MS: ${MQ_RETRY_BASE_MS:-2000}
PRESCREEN_ENABLED: ${PRESCREEN_ENABLED:-true}
PRESCREEN_AUTO_APPROVE: ${PRESCREEN_AUTO_APPROVE:-false}
PRESCREEN_CONFIDENCE_THRESHOLD: ${PRESCREEN_CONFIDENCE_THRESHOLD:-0.75}
PRESCREEN_HIGH_VALUE_THRESHOLD: ${PRESCREEN_HIGH_VALUE_THRESHOLD:-100000}
LLM_PROVIDER: ${LLM_PROVIDER:-ollama_cloud}
OLLAMA_HOST: ${OLLAMA_HOST:-}
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:14b}
OLLAMA_FALLBACK_MODEL: ${OLLAMA_FALLBACK_MODEL:-qwen2.5:7b}
OLLAMA_TEMPERATURE: ${OLLAMA_TEMPERATURE:-0.2}
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-3072}
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-120}
OLLAMA_MAX_CONCURRENCY: ${OLLAMA_MAX_CONCURRENCY:-3}
YANDEXGPT_API_KEY: ${YANDEXGPT_API_KEY:-}
YANDEXGPT_FOLDER_ID: ${YANDEXGPT_FOLDER_ID:-}
YANDEXGPT_MODEL: ${YANDEXGPT_MODEL:-yandexgpt-lite}
YANDEXGPT_FALLBACK_MODEL: ${YANDEXGPT_FALLBACK_MODEL:-}
YANDEXGPT_BASE_URL: ${YANDEXGPT_BASE_URL:-https://llm.api.cloud.yandex.net}
YANDEXGPT_COMPLETION_PATH: ${YANDEXGPT_COMPLETION_PATH:-/foundationModels/v1/completion}
YANDEXGPT_TEMPERATURE: ${YANDEXGPT_TEMPERATURE:-0.2}
YANDEXGPT_MAX_TOKENS: ${YANDEXGPT_MAX_TOKENS:-3072}
YANDEXGPT_TIMEOUT: ${YANDEXGPT_TIMEOUT:-120}
YANDEXGPT_MAX_CONCURRENCY: ${YANDEXGPT_MAX_CONCURRENCY:-3}
CHUNK_SIZE_CHARS: ${CHUNK_SIZE_CHARS:-10000}
JWT_SECRET: ${JWT_SECRET}
JWT_ALGORITHM: ${JWT_ALGORITHM:-HS256}
JWT_ACCESS_TTL_MINUTES: ${JWT_ACCESS_TTL_MINUTES:-1440}
ports:
- "9104:9104"
worker-notify:
profiles: ["services"]
build:
context: .
dockerfile: srv/worker-notify/Dockerfile
container_name: contract_check-worker-notify
restart: unless-stopped
depends_on:
rabbitmq:
condition: service_healthy
environment:
ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-contract_check}:${POSTGRES_PASSWORD:-contract_check}@postgres:5432/${POSTGRES_DB:-contract_check}
RABBITMQ_URL: amqp://${RABBITMQ_USER:-contract_check}:${RABBITMQ_PASS:-contract_check}@rabbitmq:5672/${RABBITMQ_VHOST:-/}
SENTRY_DSN: ${SENTRY_DSN:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: worker-notify
S3_ENDPOINT_URL: http://minio:9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-contract_check}
S3_SECRET_KEY: ${S3_SECRET_KEY:-contract_check}
MQ_PREFETCH_NOTIFY: ${MQ_PREFETCH_NOTIFY:-5}
MQ_MAX_ATTEMPTS: ${MQ_MAX_ATTEMPTS:-5}
MQ_RETRY_BASE_MS: ${MQ_RETRY_BASE_MS:-2000}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-no-reply@contract-check.local}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
WEB_APP_BASE_URL: ${WEB_APP_BASE_URL:-http://localhost:5173}
JWT_SECRET: ${JWT_SECRET}
JWT_ALGORITHM: ${JWT_ALGORITHM:-HS256}
JWT_ACCESS_TTL_MINUTES: ${JWT_ACCESS_TTL_MINUTES:-1440}
ports:
- "9103:9103"
# Telegram bot adapter (aiogram 3, HTTP-only to api). Per docs/ARCHITECTURE.md §17
# the bot holds no DB/MQ/S3 credentials — it depends on `api` being healthy,
# not on the infra containers directly, enforcing the hexagonal boundary even
# in dependency ordering.
bot:
profiles: ["services"]
build:
context: .
dockerfile: srv/bot/Dockerfile
container_name: contract_check-bot
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
BOT_TOKEN: ${BOT_TOKEN:-}
API_URL: ${API_URL:-http://api:8000}
BOT_SERVICE_TOKEN: ${BOT_SERVICE_TOKEN:-}
# ── EDGE (profile: edge) ──────────────────────────────────────────────────
# Reverse proxy + TLS terminator. Listens on 80/443 and forwards
# /api/v1/*, /admin/*, /metrics, /healthz, /readyz to the api service.
nginx:
profiles: ["edge"]
image: nginx:alpine
container_name: contract_check-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
# Mount exactly ONE template — rendering both would duplicate `upstream api`.
# The VPS override (deploy/vps/) swaps in contract-check-http.conf.template here.
- ./deploy/nginx/templates/contract-check.conf.template:/etc/nginx/templates/contract-check.conf.template:ro
- certbot-data:/etc/letsencrypt:ro
- certbot-webroot:/var/www/certbot:ro
environment:
NGINX_SERVER_NAME: ${NGINX_SERVER_NAME:-localhost}
NGINX_ENVSUBST_TEMPLATE_DIR: /etc/nginx/templates
NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx/conf.d
NGINX_ENVSUBST_TEMPLATE_SUFFIX: .template
healthcheck:
test: ["CMD", "wget", "-qO-", "--no-check-certificate", "http://localhost/healthz"]
interval: 10s
timeout: 3s
retries: 10
start_period: 10s
certbot:
profiles: ["edge"]
image: certbot/certbot:latest
container_name: contract_check-certbot
restart: "no"
volumes:
- certbot-data:/etc/letsencrypt
- certbot-webroot:/var/www/certbot
entrypoint: /bin/sh
command:
- -c
- |
trap exit TERM
while :; do
certbot renew --webroot-path /var/www/certbot --quiet
sleep 12h & wait $${!}
done
volumes:
pgdata:
pgwal:
redisdata:
rabbitmq:
minio:
certbot-data:
certbot-webroot: