# HR-ATS-Portal — production Compose stack (self-contained). # # docker compose up -d --build # docker compose ps # docker compose logs -f backend-api # # Public surface: only the frontend (default host port 80). The browser talks # same-origin to nginx, which proxies API paths to backend-api. Postgres, Redis, # the API, ATS engine and Taskiq workers stay on the Compose network. # # Local / current host-Postgres workflow (exposed ports, --reload, bind mounts): # docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build # # See DOCKER.md for env checklist and verification. x-logging: &default-logging driver: json-file options: max-size: "10m" max-file: "3" x-backend-build: &backend-build # Root context, not ./backend: backend/job/candidate imports the bulk-ats engine # from app/, which sits outside the backend folder. See backend/Dockerfile. context: . dockerfile: backend/Dockerfile x-backend-env: &backend-env PYTHONPATH: /app # Production default: container Postgres on the Compose network. The dev overlay # overrides DB_HOST to host.docker.internal. Credentials come from root .env so # they stay in lockstep with the postgres service (backend/.env is for host runs). DB_HOST: ${DB_HOST:-postgres} DB_PORT: ${DB_PORT:-5432} DB_USERNAME: ${DB_USERNAME:-postgres} DB_PASSWORD: ${DB_PASSWORD:-postgres} DB_NAME: ${DB_NAME:-hrms} # On every API boot: upgrade any on-disk revisions (usually none in images), # then apply ORM drift in-memory (no revision files written — versions stay # gitignored and out of the image). DB_AUTO_MIGRATE: ${DB_AUTO_MIGRATE:-true} DB_AUTOGENERATE: ${DB_AUTOGENERATE:-true} REDIS_URL: redis://redis:6379/0 # Prefer root/.env EMAIL_URL. Fall back to host-gateway when mail is on this machine. EMAIL_URL: ${EMAIL_URL:-http://host.docker.internal:5000} BACKEND_URL: http://backend-api:8000 # Shared CV storage. Named volume in production so API + workers see the same # files without a host path. Dev overlay remounts ./backend/inbox/decoded_attachments. x-attachments: &attachments - attachments-data:/app/inbox/decoded_attachments x-backend-service: &backend-service build: *backend-build image: hrms-backend:local working_dir: /app env_file: - ./backend/.env environment: *backend-env extra_hosts: - "host.docker.internal:host-gateway" depends_on: redis: condition: service_healthy postgres: condition: service_healthy restart: unless-stopped logging: *default-logging services: # --- Redis (broker + result backend for taskiq) ---------------------------------- redis: image: redis:7-alpine container_name: hrms-redis command: ["redis-server", "--appendonly", "yes"] # Not published in production. Dev overlay binds ${REDIS_PORT:-6379}:6379. volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped logging: *default-logging # --- Postgres (always on in production) ------------------------------------------ postgres: build: context: ./docker/postgres image: hrms-postgres:local container_name: hrms-postgres environment: # Interpolated from the shell or root .env, NOT from backend/.env. POSTGRES_USER: ${DB_USERNAME:-postgres} POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} POSTGRES_DB: ${DB_NAME:-hrms} POSTGRES_INITDB_ARGS: "--encoding=UTF8" # Not published in production. Dev overlay can bind 127.0.0.1:${POSTGRES_PORT:-5433}:5432. volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_NAME:-hrms}"] interval: 10s timeout: 5s retries: 5 start_period: 20s restart: unless-stopped logging: *default-logging # --- portal API ------------------------------------------------------------------ backend-api: <<: *backend-service container_name: hrms-backend-api command: [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "${UVICORN_WORKERS:-2}", "--proxy-headers", "--forwarded-allow-ips=*", ] # Not published in production — browsers reach the API via frontend nginx. volumes: *attachments healthcheck: test: [ "CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8000/health',timeout=3)", ] interval: 15s timeout: 5s retries: 5 start_period: 40s # --- bulk ATS scoring engine (standalone service form of app/) -------------------- ats-engine: build: context: . dockerfile: app/Dockerfile image: hrms-ats-engine:local container_name: hrms-ats-engine env_file: - ./backend/.env - path: ./.env required: false # Not published in production. healthcheck: test: [ "CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8100/api/v1/health',timeout=3)", ] interval: 15s timeout: 5s retries: 5 start_period: 20s restart: unless-stopped logging: *default-logging # --- React portal (only published host port) ------------------------------------- frontend: build: context: ./frontend args: # Empty = same-origin; nginx proxies API paths to backend-api (see nginx.conf). VITE_API_BASE: ${VITE_API_BASE:-} image: hrms-frontend:local container_name: hrms-frontend depends_on: backend-api: condition: service_healthy ports: - "${FRONTEND_PORT:-80}:80" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/"] interval: 15s timeout: 5s retries: 5 restart: unless-stopped logging: *default-logging # --- background processing (same image as backend-api, different command) --------- taskiq-worker: <<: *backend-service container_name: hrms-taskiq-worker command: [ "taskiq", "worker", "taskiq_management.broker_setup:broker", "inbox.tasks", "inbox.sync_tasks", "taskiq_management.tasks", "--workers", "1", ] environment: <<: *backend-env TASKIQ_QUEUE_NAME: inbox TASKIQ_WORKER_NAME: worker-01 volumes: *attachments taskiq-scheduler: <<: *backend-service container_name: hrms-taskiq-scheduler command: [ "taskiq", "scheduler", "taskiq_management.broker_setup:scheduler", "inbox.sync_tasks", ] environment: <<: *backend-env TASKIQ_QUEUE_NAME: inbox taskiq-cv-worker: <<: *backend-service container_name: hrms-taskiq-cv-worker command: [ "taskiq", "worker", "taskiq_management.cv_broker_setup:cv_broker", "inbox.cv_tasks", "--workers", "1", ] environment: <<: *backend-env TASKIQ_CV_QUEUE_NAME: cv_upload TASKIQ_WORKER_NAME: cv-worker-01 volumes: *attachments taskiq-cv-scheduler: <<: *backend-service container_name: hrms-taskiq-cv-scheduler command: [ "taskiq", "scheduler", "taskiq_management.cv_broker_setup:cv_scheduler", "inbox.cv_tasks", ] environment: <<: *backend-env TASKIQ_CV_QUEUE_NAME: cv_upload # Dedicated stream: Outlook pull/triage must not block match/ATS or CV uploads. taskiq-mailbox-sync-worker: <<: *backend-service container_name: hrms-taskiq-mailbox-sync-worker command: [ "taskiq", "worker", "taskiq_management.mailbox_sync_broker_setup:mailbox_sync_broker", "inbox.mailbox_sync_tasks", "--workers", "1", ] environment: <<: *backend-env TASKIQ_MAILBOX_SYNC_QUEUE_NAME: mailbox_sync TASKIQ_WORKER_NAME: mailbox-sync-worker-01 volumes: *attachments volumes: redis-data: postgres-data: attachments-data: