HR-ATS-Portal/docker-compose.yml

283 lines
8.1 KiB
YAML

# 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: