# «Контракт-чек» app services on Nomad (single VPS, docker driver). # # Groups: api (with prestart migrations) + 5 workers. Stateful infra # (postgres/redis/rabbitmq/minio) stays on compose; tasks reach it via the # docker0 host gateway 172.17.0.1 and the host-published ports # (15432/17379/5672/9000). The nginx edge cascade is unchanged. # # Env: the compose .env file (~/DealDocumentScreening/.env on the VPS) is # bind-mounted into every container at /secrets/.env and sourced before the # app starts. A sed one-liner rewrites compose hostnames (postgres, redis, # rabbitmq, minio) to 172.17.0.1 + published ports so the connections work # from outside the compose network. # # Image tag uses ${IMAGE_TAG}, rendered by envsubst at submit time. # Registry auth: host-level `docker login` on the VPS (README §5). job "contract-check" { datacenters = ["vps"] type = "service" update { max_parallel = 1 min_healthy_time = "15s" healthy_deadline = "5m" progress_deadline = "10m" auto_revert = true } # ══ API ════════════════════════════════════════════════════════════════════ group "api" { count = 1 update { max_parallel = 1 } restart { attempts = 3 interval = "10m" delay = "15s" mode = "delay" } network { port "http" { static = 18000 to = 8000 } } service { name = "contract-check-api" port = "http" provider = "nomad" check { name = "healthz" type = "http" path = "/healthz" interval = "10s" timeout = "3s" } check_restart { limit = 3 grace = "30s" } } # One-shot migrations before the API starts. task "migrate" { lifecycle { hook = "prestart" } driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-api:${IMAGE_TAG}" command = "sh" args = [ "-c", "set -a; . /secrets/.env; set +a; exec alembic upgrade head", ] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } resources { cpu = 150 memory = 256 } } task "api" { driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-api:${IMAGE_TAG}" command = "sh" args = [ "-c", <<-SH set -a; . /secrets/.env; set +a export DATABASE_URL="$${DATABASE_URL/@postgres:/@172.17.0.1:15432:}" export REDIS_URL="$${REDIS_URL/@redis:/@172.17.0.1:17379:}" export RABBITMQ_URL="$${RABBITMQ_URL/@rabbitmq:/@172.17.0.1:5672:}" export S3_ENDPOINT_URL="http://172.17.0.1:9000" export APP_VERSION="${IMAGE_TAG}" exec python -m src.contract_check.api SH ] ports = ["http"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } kill_timeout = "30s" resources { cpu = 300 memory = 512 } } } # ══ WORKERS ═══════════════════════════════════════════════════════════════ group "worker-extract" { count = 1 update { canary = 1 auto_promote = true } restart { attempts = 3 interval = "10m" delay = "15s" mode = "delay" } task "worker-extract" { driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-worker-extract:${IMAGE_TAG}" command = "sh" args = [ "-c", <<-SH set -a; . /secrets/.env; set +a export DATABASE_URL="$${DATABASE_URL/@postgres:/@172.17.0.1:15432:}" export RABBITMQ_URL="$${RABBITMQ_URL/@rabbitmq:/@172.17.0.1:5672:}" export S3_ENDPOINT_URL="http://172.17.0.1:9000" export APP_VERSION="${IMAGE_TAG}" exec python -m src.contract_check.worker_extract SH ] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } kill_timeout = "60s" resources { cpu = 500 memory = 640 } } } group "worker-analyze" { count = 1 update { canary = 1 auto_promote = true } restart { attempts = 3 interval = "10m" delay = "15s" mode = "delay" } task "worker-analyze" { driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-worker-analyze:${IMAGE_TAG}" command = "sh" args = [ "-c", <<-SH set -a; . /secrets/.env; set +a export DATABASE_URL="$${DATABASE_URL/@postgres:/@172.17.0.1:15432:}" export RABBITMQ_URL="$${RABBITMQ_URL/@rabbitmq:/@172.17.0.1:5672:}" export S3_ENDPOINT_URL="http://172.17.0.1:9000" export APP_VERSION="${IMAGE_TAG}" exec python -m src.contract_check.worker_analyze SH ] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } kill_timeout = "120s" resources { cpu = 250 memory = 384 } } } group "worker-prescreen" { count = 1 update { canary = 1 auto_promote = true } restart { attempts = 3 interval = "10m" delay = "15s" mode = "delay" } task "worker-prescreen" { driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-worker-prescreen:${IMAGE_TAG}" command = "sh" args = [ "-c", <<-SH set -a; . /secrets/.env; set +a export DATABASE_URL="$${DATABASE_URL/@postgres:/@172.17.0.1:15432:}" export RABBITMQ_URL="$${RABBITMQ_URL/@rabbitmq:/@172.17.0.1:5672:}" export S3_ENDPOINT_URL="http://172.17.0.1:9000" export APP_VERSION="${IMAGE_TAG}" exec python -m src.contract_check.worker_prescreen SH ] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } kill_timeout = "60s" resources { cpu = 150 memory = 256 } } } group "worker-billing" { count = 1 update { canary = 1 auto_promote = true } restart { attempts = 3 interval = "10m" delay = "15s" mode = "delay" } task "worker-billing" { driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-worker-billing:${IMAGE_TAG}" command = "sh" args = [ "-c", <<-SH set -a; . /secrets/.env; set +a export DATABASE_URL="$${DATABASE_URL/@postgres:/@172.17.0.1:15432:}" export APP_VERSION="${IMAGE_TAG}" exec python -m src.contract_check.worker_billing SH ] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } kill_timeout = "60s" resources { cpu = 150 memory = 256 } } } group "worker-notify" { count = 1 update { canary = 1 auto_promote = true } restart { attempts = 3 interval = "10m" delay = "15s" mode = "delay" } task "worker-notify" { driver = "docker" config { image = "p2gnl.mu-dungeon.xyz/admin-git/contract-check-worker-notify:${IMAGE_TAG}" command = "sh" args = [ "-c", <<-SH set -a; . /secrets/.env; set +a export DATABASE_URL="$${DATABASE_URL/@postgres:/@172.17.0.1:15432:}" export RABBITMQ_URL="$${RABBITMQ_URL/@rabbitmq:/@172.17.0.1:5672:}" export APP_VERSION="${IMAGE_TAG}" exec python -m src.contract_check.worker_notify SH ] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env" readonly = true }, ] } kill_timeout = "60s" resources { cpu = 150 memory = 256 } } } }