# «Контракт-чек» 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: a dedicated .env.nomad (copy of compose .env with hostnames rewritten # to 172.17.0.1 + published ports) is bind-mounted at /secrets/.env and # sourced before the app starts. Create it once on the VPS: # cp ~/DealDocumentScreening/.env ~/DealDocumentScreening/.env.nomad # # edit: @postgres:5432 → @172.17.0.1:15432, @redis:6379 → @172.17.0.1:17379, # # @rabbitmq:5672 → @172.17.0.1:5672, http://minio:9000 → http://172.17.0.1:9000 # # 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" } } 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.nomad" 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", "set -a; . /secrets/.env; set +a; export APP_VERSION=${IMAGE_TAG}; exec python -m src.contract_check.api"] ports = ["http"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env.nomad" 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", "set -a; . /secrets/.env; set +a; export APP_VERSION=${IMAGE_TAG}; exec python -m src.contract_check.worker_extract"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env.nomad" 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", "set -a; . /secrets/.env; set +a; export APP_VERSION=${IMAGE_TAG}; exec python -m src.contract_check.worker_analyze"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env.nomad" 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", "set -a; . /secrets/.env; set +a; export APP_VERSION=${IMAGE_TAG}; exec python -m src.contract_check.worker_prescreen"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env.nomad" 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", "set -a; . /secrets/.env; set +a; export APP_VERSION=${IMAGE_TAG}; exec python -m src.contract_check.worker_billing"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env.nomad" 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", "set -a; . /secrets/.env; set +a; export APP_VERSION=${IMAGE_TAG}; exec python -m src.contract_check.worker_notify"] mounts = [ { type = "bind" target = "/secrets/.env" source = "/root/DealDocumentScreening/.env.nomad" readonly = true } ] } kill_timeout = "60s" resources { cpu = 150 memory = 256 } } } }