274 lines
9.7 KiB
YAML
274 lines
9.7 KiB
YAML
# HR-ATS-Portal — every service and every image, in one file.
|
|
#
|
|
# docker compose build # hrms-backend / hrms-ats-engine / hrms-frontend
|
|
# docker compose up -d
|
|
# docker compose ps
|
|
# docker compose logs -f backend-api
|
|
#
|
|
# ── Postgres ──────────────────────────────────────────────────────────────────────
|
|
# The `postgres` service at the bottom is behind a compose PROFILE, so it is defined
|
|
# and buildable here but never starts with a plain `docker compose up`. The stack
|
|
# talks to the PostgreSQL server already running on the host, via
|
|
# DB_HOST=host.docker.internal (backend/.env says localhost — correct for a host
|
|
# process, wrong inside a container).
|
|
#
|
|
# Host Postgres must accept connections from the Docker bridge: listen_addresses = '*'
|
|
# in postgresql.conf and a pg_hba.conf line for 172.16.0.0/12 (or the specific subnet).
|
|
#
|
|
# To build/run the containerised database instead, see the comments on that service.
|
|
#
|
|
# ── Ports ─────────────────────────────────────────────────────────────────────────
|
|
# Stop a host `uvicorn` (8000) and `npm run dev` (5173) before starting these, or
|
|
# override with BACKEND_PORT / FRONTEND_PORT / ATS_PORT. Windows lets a host process
|
|
# bind 127.0.0.1:8000 while Docker binds 0.0.0.0:8000, and `localhost` resolves to ::1
|
|
# first — both listen, and requests reach whichever won.
|
|
#
|
|
# ── Overlay ───────────────────────────────────────────────────────────────────────
|
|
# docker-compose.dev.yml adds live source mounts and --reload on top of this file. It
|
|
# defines no services or images of its own; it only overrides the ones here:
|
|
# docker compose -f docker-compose.yml -f docker-compose.dev.yml up
|
|
|
|
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
|
|
# backend/.env is written for host processes; these are the values a container needs.
|
|
#
|
|
# DB_HOST defaults to the LOCAL Postgres server on the host. Set DB_HOST=postgres in
|
|
# the shell (or a root .env) to point the whole stack at the container below instead
|
|
# — that is the only value that has to change, since services reach it over the
|
|
# compose network on 5432, not the published host port.
|
|
DB_HOST: ${DB_HOST:-host.docker.internal}
|
|
REDIS_URL: redis://redis:6379/0
|
|
EMAIL_URL: http://host.docker.internal:5000
|
|
BACKEND_URL: http://backend-api:8000
|
|
|
|
# The one shared folder. Every process that decodes, scores or serves a CV reads and
|
|
# writes the same host directory, so a file written by the API is the same file the
|
|
# worker opens. Absolute paths stored in the DB match across services because the
|
|
# mount target is identical everywhere; inbox.plugins.resolve_attachment_path also
|
|
# falls back to basename-under-this-directory for rows written by a host process.
|
|
x-attachments: &attachments
|
|
- ${ATTACHMENTS_DIR:-./backend/inbox/decoded_attachments}:/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
|
|
# required:false — the default stack uses the host's Postgres and never starts
|
|
# this one, and that must not be an error. When the profile IS active, startup
|
|
# waits for it to pass pg_isready.
|
|
postgres:
|
|
condition: service_healthy
|
|
required: false
|
|
restart: unless-stopped
|
|
|
|
services:
|
|
# --- Redis (broker + result backend for taskiq) ----------------------------------
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: hrms-redis
|
|
command: ["redis-server", "--appendonly", "yes"]
|
|
ports:
|
|
- "${REDIS_PORT:-6379}:6379"
|
|
volumes:
|
|
- redis-data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
restart: unless-stopped
|
|
|
|
# --- portal API ------------------------------------------------------------------
|
|
backend-api:
|
|
<<: *backend-service
|
|
container_name: hrms-backend-api
|
|
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
ports:
|
|
- "${BACKEND_PORT:-8000}:8000"
|
|
volumes: *attachments
|
|
healthcheck:
|
|
# python:slim has no curl and the API exposes no /health route, so this is a
|
|
# plain TCP check against the uvicorn socket.
|
|
test:
|
|
[
|
|
"CMD",
|
|
"python",
|
|
"-c",
|
|
"import socket;socket.create_connection(('127.0.0.1',8000),3).close()",
|
|
]
|
|
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:
|
|
# OPENAI_API_KEY currently lives in backend/.env; a root .env (see .env.example)
|
|
# overrides it when present, and the stack still comes up when it is not.
|
|
- ./backend/.env
|
|
- path: ./.env
|
|
required: false
|
|
ports:
|
|
- "${ATS_PORT:-8100}:8100"
|
|
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
|
|
|
|
# --- React portal -----------------------------------------------------------------
|
|
frontend:
|
|
build:
|
|
context: ./frontend
|
|
args:
|
|
# Baked into the bundle at build time — change it and rebuild, not restart.
|
|
VITE_API_BASE: ${VITE_API_BASE:-http://localhost:8000}
|
|
image: hrms-frontend:local
|
|
container_name: hrms-frontend
|
|
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
|
|
|
|
# --- 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
|
|
|
|
# --- Postgres: DEFINED HERE, NOT STARTED BY DEFAULT -------------------------------
|
|
# The profile is what keeps it out of `docker compose build` and `docker compose up`.
|
|
# Nothing about the default stack changes by its presence in this file.
|
|
#
|
|
# docker compose --profile postgres build postgres # build the image
|
|
# docker compose --profile postgres up -d postgres # run it, host port 5433
|
|
#
|
|
# Pointing the app at it is a separate, deliberate step — set DB_HOST=postgres (see
|
|
# x-backend-env) and recreate the services. The volume starts empty, so Alembic
|
|
# rebuilds the schema on first boot; it does not share the host server's data.
|
|
postgres:
|
|
profiles: ["postgres"]
|
|
build:
|
|
context: ./docker/postgres
|
|
image: hrms-postgres:local
|
|
container_name: hrms-postgres
|
|
environment:
|
|
# Interpolated from the shell or a root .env, NOT from backend/.env — compose
|
|
# variable substitution and container environment are different things.
|
|
# Defaults match backend/.env.example.
|
|
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 server owns 5432. Only for host-side tools —
|
|
# containers reach this one on 5432 over the compose network.
|
|
- "${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
|
|
|
|
volumes:
|
|
redis-data:
|
|
postgres-data:
|