-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.prod.yml
More file actions
181 lines (168 loc) · 7.3 KB
/
Copy pathdocker-compose.prod.yml
File metadata and controls
181 lines (168 loc) · 7.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
services:
biometric-api:
build:
context: .
dockerfile: Dockerfile
container_name: biometric-api
restart: unless-stopped
# Hardening (IN-H4 — 2026-04-19):
# - read_only rootfs; writable dirs are explicit named volumes
# (/app/uploads, /tmp/.deepface) and tmpfs (/tmp, /var/log).
# - NUMBA_CACHE_DIR=/tmp/numba_cache is already under the /tmp tmpfs.
# - cap_drop ALL: the uvicorn workload (uid 100) needs no capabilities.
# The entrypoint, however, starts as root to chown the mounted cache
# volume and then drops to uid 100 via `gosu`. Under cap_drop ALL even
# root lacks CHOWN/SETUID/SETGID, so gosu failed with
# "failed switching to app: operation not permitted" and the container
# crash-looped on every rebuild (2026-05-29). Re-add exactly the three
# caps the root entrypoint needs; everything else stays dropped.
read_only: true
tmpfs:
- /tmp
- /var/log
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
security_opt:
- no-new-privileges:true
environment:
APP_NAME: "Biometric Processor API"
VERSION: "1.0.0"
ENVIRONMENT: production
LOG_LEVEL: INFO
LOG_FORMAT: json
API_HOST: 0.0.0.0
API_PORT: 8001
PORT: 8001
NUMBA_DISABLE_JIT: "1"
NUMBA_CACHE_DIR: /tmp/numba_cache
# Shared PostgreSQL (pgvector enabled on biometric_db)
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@shared-postgres:5432/biometric_db
DATABASE_POOL_MIN_SIZE: 2
DATABASE_POOL_MAX_SIZE: 10
EMBEDDING_DIMENSION: 512
# Shared Redis (database 4 for biometric)
REDIS_HOST: shared-redis
REDIS_PORT: 6379
REDIS_DB: 4
REDIS_PASSWORD: ${REDIS_PASSWORD}
REDIS_MAX_CONNECTIONS: 20
# ML Configuration — CPU-optimized, lightweight
FACE_DETECTION_BACKEND: mtcnn
FACE_RECOGNITION_MODEL: Facenet512
ANTI_SPOOFING_ENABLED: "true"
ANTI_SPOOFING_THRESHOLD: "0.5"
LIVENESS_UNIFACE_DEFAULT_ENABLED: "True"
# Passive deep-learning liveness only (UniFace MiniFASNet). 'hybrid'
# combines this with EnhancedLivenessDetector which expects an active
# blink+smile challenge — appropriate for /enroll, but /verify uses
# a single still capture with no active prompts, so the combined
# detector vetoed every legit user with challenge_completed=False.
LIVENESS_MODE: passive
LIVENESS_BACKEND: uniface
# UniFace MiniFASNet caches its ONNX weights under ~/.uniface/models by
# default. Container HOME=/nonexistent and rootfs is read_only:true, so
# point the cache at a writable named volume to persist across restarts.
UNIFACE_CACHE_DIR: /app/uniface-cache
HOME: /tmp
ASYNC_ML_ENABLED: true
ML_THREAD_POOL_SIZE: 2
EMBEDDING_CACHE_ENABLED: true
EMBEDDING_CACHE_TTL: 300
EMBEDDING_CACHE_MAX_SIZE: 500
# Security
CORS_ORIGINS: '["https://app.fivucsas.com","https://fivucsas.com","https://api.fivucsas.com","https://verify.fivucsas.com","https://demo.fivucsas.com"]'
API_KEY_ENABLED: "true"
API_KEY_REQUIRE_AUTH: "true"
API_KEY_SECRET: ${API_KEY_SECRET}
REQUIRE_HTTPS: "true"
JWT_ENABLED: "true"
JWT_SECRET: ${JWT_SECRET}
# Fernet embedding encryption (P1.3 — 2026-05-02). Container fails fast
# if missing because the embedding repository unconditionally builds an
# EmbeddingCipher in its __init__.
FIVUCSAS_EMBEDDING_KEY: ${FIVUCSAS_EMBEDDING_KEY}
FIVUCSAS_EMBEDDING_KEY_VERSION: ${FIVUCSAS_EMBEDDING_KEY_VERSION}
# Anti-spoof feature flags (PR #88, 2026-05-09). All default OFF
# so prod behaviour is unchanged until an operator opts in via
# .env.prod. The runtime imports these gates from the
# spoof_detector pip package (≥ v0.2.1).
ANTISPOOF_DEVICE_RISK_ENABLED: ${ANTISPOOF_DEVICE_RISK_ENABLED:-false}
ANTISPOOF_USABILITY_GATE_ENABLED: ${ANTISPOOF_USABILITY_GATE_ENABLED:-false}
ANTISPOOF_FUSION_ENABLED: ${ANTISPOOF_FUSION_ENABLED:-false}
ANTISPOOF_CUTOUT_ENABLED: ${ANTISPOOF_CUTOUT_ENABLED:-false}
# Rate Limiting
RATE_LIMIT_ENABLED: true
RATE_LIMIT_STORAGE: memory
RATE_LIMIT_PER_MINUTE: 60
# File Storage
UPLOAD_FOLDER: /app/uploads
MAX_UPLOAD_SIZE: 10485760
# Quality Thresholds
VERIFICATION_THRESHOLD: 0.4
QUALITY_THRESHOLD: 40.0
BLUR_THRESHOLD: 15.0
MIN_FACE_SIZE: 60
# Demo UI disabled in production (security)
DEMO_UI_ENABLED: "false"
# TensorFlow
TF_CPP_MIN_LOG_LEVEL: "2"
DEEPFACE_HOME: /tmp/.deepface
# DeepFace model-integrity pin. The image fail-closes on boot if the
# Facenet512 weights SHA256 isn't pinned (DEEPFACE_SHA256_REQUIRED=true).
# These live in .env.prod; the compose has no env_file, so they must be
# forwarded explicitly via interpolation or the container sees them empty
# (caused a crash-loop on the 2026-05-29 rebuild). Pass-through from .env.prod:
DEEPFACE_FACENET512_SHA256: ${DEEPFACE_FACENET512_SHA256}
DEEPFACE_SHA256_REQUIRED: ${DEEPFACE_SHA256_REQUIRED:-true}
volumes:
- biometric_uploads:/app/uploads
# /tmp/.deepface — DeepFace weights cache. As of 2026-05-12 the four
# critical model files (Facenet512, centerface, MiniFASNetV2,
# MiniFASNetV1SE) are BAKED INTO THE IMAGE under /opt/baked-models/
# and the entrypoint shim self-heals this volume from that source on
# boot (chown to 100:101 + cp missing weights). Two consequences:
# 1) Removing this volume mount is now safe — DeepFace will read
# directly from the image layer (DEEPFACE_HOME=/tmp/.deepface
# with no overlay means the read_only:true rootfs blocks writes,
# but reads succeed). We keep it for now so DeepFace can still
# cache additional models it may want to download in the future
# (e.g. new MiniFASNet variants in a DeepFace upgrade).
# 2) `docker volume rm biometric-processor_biometric_models` is now
# a no-op for correctness: the entrypoint shim seeds it again
# from the baked image layer on the next boot. Operator memory
# ("remember to docker cp the .pth files") is no longer
# load-bearing. See feedback_readonly_rootfs_cache_dirs.
- biometric_models:/tmp/.deepface
- biometric_uniface:/app/uniface-cache
# No Traefik labels by design (CLAUDE.md: "Internal only — No public
# Traefik route. bio.fivucsas.com is NOT publicly accessible"). The only
# caller is identity-core-api, which reaches us at http://biometric-api:8001
# over the shared `backend` docker network. The earlier 2026-04-28 P0
# finding ("public exposure without middlewares") is closed by *removing*
# the public router rather than middleware-gating it. To debug from
# outside, port-forward via `docker exec` or set up a one-off SSH tunnel.
networks:
- backend
deploy:
resources:
limits:
cpus: "4.0"
memory: 4G
pids: 256
reservations:
cpus: '0.5'
memory: 2.5G
networks:
backend:
external: true
volumes:
biometric_uploads:
driver: local
biometric_models:
driver: local
biometric_uniface:
driver: local