329 lines
9.7 KiB
YAML
329 lines
9.7 KiB
YAML
# HR-ATS-Portal — single Compose file for local and production.
|
|
#
|
|
# docker compose up -d --build
|
|
#
|
|
# Root `.env` only sets COMPOSE_ENV_FILES=./backend/.env so ${FRONTEND_PORT},
|
|
# ${BACKEND_PORT}, ${UVICORN_WORKERS}, … interpolate from backend/.env.
|
|
# Secrets and app config live solely in backend/.env (loaded into containers
|
|
# via env_file as well).
|
|
#
|
|
# Local (PROD_ENV=false, DB_HOST=localhost in backend/.env):
|
|
# Containers reach host Postgres via host.docker.internal (db_setup rewrite
|
|
# when IN_DOCKER=1). Frontend :5173, API :8000 by default.
|
|
#
|
|
# Prod (PROD_ENV=true, DB_* = RDS in backend/.env — edit manually):
|
|
# Same command. No host rewrite; SSL require when DB_SSLMODE is blank.
|
|
#
|
|
# Optional Compose Postgres:
|
|
# docker compose --profile postgres up -d postgres
|
|
#
|
|
# Optional live-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
|
|
# Lets db_setup rewrite DB_HOST=localhost → host.docker.internal (local only).
|
|
IN_DOCKER: "1"
|
|
# Credentials and endpoints come from backend/.env via `env_file` below.
|
|
# DB_HOST is deliberately ABSENT here: an empty `environment:` override would
|
|
# blank env_file / RDS. Do not name credential keys under environment.
|
|
#
|
|
# REDIS_URL and BACKEND_URL are compose-network DNS names, correct everywhere.
|
|
REDIS_URL: redis://redis:6379/0
|
|
BACKEND_URL: http://backend-api:8000
|
|
# Apify token, if kept at repo root rather than in backend/.env. Harmless when
|
|
# unset: the app treats empty as absent.
|
|
APIFY_API_TOKEN: ${APIFY_API_TOKEN:-${APIFY_TOKEN:-}}
|
|
|
|
# Shared CV storage. Named volume so API + workers see the same files.
|
|
# Optional docker-compose.dev.yml 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 is the source of truth (plain DB_* + PROD_ENV).
|
|
- ./backend/.env
|
|
# Optional local overrides (required:false). Do not set DB_HOST=postgres
|
|
# here unless you intentionally start the postgres profile.
|
|
- path: ./docker.local.env
|
|
required: false
|
|
environment: *backend-env
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
depends_on:
|
|
redis:
|
|
condition: service_healthy
|
|
# required:false — default stack never starts postgres (host Postgres or RDS).
|
|
postgres:
|
|
condition: service_healthy
|
|
required: false
|
|
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"]
|
|
# No host publish by default (Compose DNS redis:6379). Optional loopback:
|
|
# docker-compose.host-ports.yml
|
|
volumes:
|
|
- redis-data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
restart: unless-stopped
|
|
logging: *default-logging
|
|
|
|
# --- Postgres: DEFINED HERE, NOT STARTED BY DEFAULT -------------------------------
|
|
# Default local path is host Postgres (DB_HOST=localhost → host.docker.internal).
|
|
# Prod uses AWS RDS. Opt in only when you want a disposable Compose DB:
|
|
#
|
|
# docker compose --profile postgres up -d postgres
|
|
# # then set DB_HOST=postgres in backend/.env and recreate backend services
|
|
#
|
|
postgres:
|
|
profiles: ["postgres"]
|
|
build:
|
|
context: ./docker/postgres
|
|
image: hrms-postgres:local
|
|
container_name: hrms-postgres
|
|
environment:
|
|
# Compose substitution needs: docker compose --env-file ./backend/.env …
|
|
# (there is no repo-root .env). Defaults apply if the flag is omitted.
|
|
POSTGRES_USER: ${DB_USERNAME:-postgres}
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
|
|
POSTGRES_DB: ${DB_NAME:-hrms}
|
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
|
|
ports:
|
|
# 5433: the host's own Postgres owns 5432.
|
|
- "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=*",
|
|
]
|
|
# Published for host tools / Vite (`VITE_API_BASE=http://127.0.0.1:8000`).
|
|
ports:
|
|
- "${BACKEND_PORT:-8000}:8000"
|
|
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: 12
|
|
# RDS / first-boot Alembic can run well past 40s before /health answers.
|
|
start_period: 180s
|
|
|
|
# --- 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: ./docker.local.env
|
|
required: false
|
|
environment:
|
|
IN_DOCKER: "1"
|
|
# No host publish by default (backend uses ats-engine:8100). Optional:
|
|
# docker-compose.host-ports.yml
|
|
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
|
|
|
|
# Portal on FRONTEND_PORT (default 5173). API also on BACKEND_PORT (8000).
|
|
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:-5173}: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: Google Sheet → FormData import must not block inbox/CV/mailbox.
|
|
taskiq-sheet-worker:
|
|
<<: *backend-service
|
|
container_name: hrms-taskiq-sheet-worker
|
|
command:
|
|
[
|
|
"taskiq",
|
|
"worker",
|
|
"taskiq_management.g_sheet_broker_setup:sheet_broker",
|
|
"g_sheet.tasks",
|
|
"--workers",
|
|
"1",
|
|
]
|
|
environment:
|
|
<<: *backend-env
|
|
TASKIQ_SHEET_QUEUE_NAME: sheet_import
|
|
TASKIQ_WORKER_NAME: sheet-worker-01
|
|
|
|
# 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:
|