Deployment¶
Docker Compose (Development)¶
Starts the local stack: bootstrap, API, Admin, PostgreSQL, and MinIO.
The checked-in compose file is for local development. It does not target production domains by default. Its default CORS setting allows only local admin origins, and the admin container waits for a healthy API container before starting.
The bootstrap service is a one-shot setup job that prepares schema and storage prerequisites before the long-running services start.
Services¶
| Service | Build | Image | Ports |
|---|---|---|---|
bootstrap |
./Dockerfile |
python:3.14-slim + uv | — |
api |
./Dockerfile |
python:3.14-slim + uv | 8000 |
admin |
./admin/Dockerfile |
node:24-slim → node:24-alpine | 4321 |
db |
— | postgres:18-alpine | 5432 |
minio |
— | minio/minio:RELEASE.2025-04-22T22-12-26Z | 9000, 9001 |
Build Args¶
FACE_API_MODEL: Model to download at build time (default:scrfd_10g)PUBLIC_API_URL(admin build): defaults tohttp://localhost:8000in the local compose stack so the admin UI never targets production by accident
Admin runtime env¶
ADMIN_SESSION_API_URL: defaults to the internal API service URL used by the Node session-gating server for auth checks
Volumes¶
pgdata: PostgreSQL data directoryminio-data: MinIO object storage
Production Configuration¶
API Service¶
environment:
FACE_API_MODEL: scrfd_10g
FACE_API_STORAGE_ENABLED: "true"
FACE_API_DATABASE_URL: "postgresql+asyncpg://user:pass@db-host:5432/faceapi"
FACE_API_S3_ENDPOINT: "https://s3.amazonaws.com"
FACE_API_S3_ACCESS_KEY: "${AWS_ACCESS_KEY_ID}"
FACE_API_S3_SECRET_KEY: "${AWS_SECRET_ACCESS_KEY}"
FACE_API_S3_BUCKET: "xylolabs-face-api-prod"
FACE_API_S3_REGION: "us-east-1"
FACE_API_S3_PUBLIC_ENDPOINT: "https://static.face-api.xylolabs.com"
FACE_API_CORS_ORIGINS: "https://face-api-admin.xylolabs.com"
FACE_API_ADMIN_SESSION_SECRET: "${FACE_API_ADMIN_SESSION_SECRET}"
FACE_API_ADMIN_SESSION_TTL_SECONDS: "43200"
FACE_API_ADMIN_LOGIN_RATE_LIMIT: "10/minute"
FACE_API_ORT_THREADS: "2"
Admin Service¶
build:
context: ./admin
args:
PUBLIC_API_URL: "https://face-api.xylolabs.com"
environment:
ADMIN_SESSION_API_URL: "http://api:8000"
PUBLIC_API_URLshould point to the browser-facing API origin.ADMIN_SESSION_API_URLshould point to the network path the Node session-gating server can actually reach from inside the container/runtime.
Concurrency Tuning¶
| Variable | Default | Description |
|---|---|---|
FACE_API_MAX_CONCURRENT_INFERENCE |
2 |
Max parallel ONNX inference calls per worker |
FACE_API_MAX_CONCURRENT_REQUESTS |
10 |
Max in-flight requests before 503 |
FACE_API_ORT_THREADS |
0 (auto) |
ONNX Runtime threads per inference call |
Scaling¶
- Workers:
--workers Nin uvicorn CMD (default: 1). Each worker loads the model separately (~200MB). - ORT Threads: Set to core count (e.g.,
FACE_API_ORT_THREADS=2for 2 cores). - Inference Concurrency: Keep
MAX_CONCURRENT_INFERENCE<= core count to avoid CPU thrashing. - DB Pool: SQLAlchemy
pool_size=10, max_overflow=20(configured indatabase.py).
Health Check¶
using var client = new HttpClient();
var response = await client.GetAsync("http://localhost:8000/health");
Console.WriteLine(await response.Content.ReadAsStringAsync());
Docker HEALTHCHECK runs every 30s with 10s startup grace period.
Domain Routing¶
| Domain | Target |
|---|---|
face-api.xylolabs.com |
API service :8000 |
face-api-admin.xylolabs.com |
Admin service :4321 |
docs.face-api.xylolabs.com |
MkDocs static site |
static.face-api.xylolabs.com |
Public processed-asset host backed by MinIO when configured |
Use a reverse proxy (nginx, Caddy, Traefik) or cloud load balancer that matches your own environment. The repository does not provision reverse-proxy config, TLS certificates, DNS, or firewall rules for you.
Production (Oracle Cloud)¶
This section is an operator note for the currently documented manual environment. It does not describe infrastructure that the repository can create or verify on its own.
The API is deployed at 130.162.132.159 (Oracle Cloud, Ampere Altra ARM, 2 cores / 4GB RAM).
Infrastructure¶
| Component | Details |
|---|---|
| VM | OCI Ampere A1 (aarch64), Ubuntu 24.04 |
| Docker | Docker Engine 29.x + Compose v5.x |
| Reverse Proxy | nginx 1.24 with HTTP→HTTPS redirect |
| SSL | Let's Encrypt via certbot (auto-renews) |
| Domains | face-api.xylolabs.com, face-api-admin.xylolabs.com, docs.face-api.xylolabs.com, static.face-api.xylolabs.com |
Tuning for 2-core/4GB¶
# docker-compose.yml overrides
FACE_API_MAX_CONCURRENT_INFERENCE: "1"
FACE_API_MAX_CONCURRENT_REQUESTS: "8"
FACE_API_ORT_THREADS: "2"
# Dockerfile: --workers 1 (avoid double model loading)
# Memory limit: 768MB for API container
SSH Access¶
Redeploy¶
# From local machine
rsync -avz --exclude='.venv' --exclude='.git' --exclude='models/*.onnx' \
--exclude='node_modules' --exclude='__pycache__' \
-e "ssh -i ~/.ssh/xylolabs-prod.pem" \
./ ubuntu@130.162.132.159:~/face-api/
# On remote
cd ~/face-api && docker compose up -d --build
Nginx Config¶
If you use nginx in an environment like the current Oracle Cloud deployment, the active config lives outside the repo (for example /etc/nginx/sites-available/face-api.conf). Certbot-managed SSL directives are also external runtime state, not versioned here.
Firewall¶
Ports 22, 80, and 443 must be opened in whatever host firewall and cloud-network controls your environment uses. In the current Oracle Cloud deployment that means both host-level rules and OCI Security List ingress.
Environment Variables¶
Full list in API Documentation (English).
Migration Path¶
The normal serving app no longer mutates schema on every startup. Local/dev bootstrap is handled explicitly by scripts/bootstrap_runtime.py (and by the one-shot bootstrap service in docker-compose.yml).
For production migrations:
- Add Alembic:
uv add alembic - Initialize:
alembic init migrations - Configure
migrations/env.pyto use async engine - Generate migrations:
alembic revision --autogenerate -m "description" - Apply:
alembic upgrade head
The PostgreSQL service in docker-compose is designed to be migrated to a separate database server — just update FACE_API_DATABASE_URL.