-
Notifications
You must be signed in to change notification settings - Fork 2
Deployment
Hosting the whole app yourself, frontend and backend, rather than using the hosted frontend with a local backend.
Face Gallery has no authentication. Anyone who can reach the backend can read, upload, and delete every photo on it. Do not put it on the public internet without putting an authenticating proxy in front.
cd face-detection-webapp/frontend
npm ci
npm run buildStatic files land in dist/. Serve them with nginx, Caddy, or anything else.
The build needs 1 to 2 GB of memory. On a small VPS, build locally and upload
dist/ rather than building on the box.
cd face-detection-webapp/backend
FACE_GALLERY_ALLOWED_ORIGINS="https://photos.example.com" \
FACE_GALLERY_DATA_DIR=/var/lib/face-gallery \
.venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8000Set FACE_GALLERY_ALLOWED_ORIGINS to the origin serving your frontend, or
every browser request will be blocked. See Configuration.
Do not run multiple workers. Running processes such as
gunicorn -w 4 will break the app.
The registry of running jobs is an in-memory dictionary, and the processing settings are module-level globals. With more than one worker, a pause or stop request often lands on a process that knows nothing about the job and returns 404, WebSocket connections attach to the wrong process, and a settings change applies to whichever worker served it. One worker is the only correct configuration today.
The workload is CPU-bound inside dlib, so extra workers would not buy much anyway.
The job registry is in memory, so a restart orphans anything mid-run: it stays
processing forever and cannot be resumed. Delete such jobs and start over.
Under systemd, a unit with Restart=on-failure keeps the service up but does
not fix orphaned jobs.
Point the proxy at port 8000 and serve frontend/dist/ as static files. A
single-page app needs a fallback so client-side routes work on a refresh:
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}Without the fallback, reloading on /results/<id> returns 404. Asset filenames
are content-hashed, so they are safe to cache indefinitely; index.html must
not be, or a deploy will not reach browsers.
Also proxy /api, /ws, /thumb, and /static to the backend, and make sure
/ws carries the WebSocket upgrade headers.
Measured on 1080p and 12 MP photos:
| Situation | Peak memory |
|---|---|
| Idle, dlib models loaded | 216 MB |
| Processing a 2 MP photo | 245 MB |
| Processing a 12 MP photo | 388 MB |
| 20 concurrent thumbnail requests, 12 MP | 1.3 GB |
| 40 concurrent thumbnail requests, 12 MP | 2.2 GB |
Memory is not the constraint; the worst case comes from thumbnail generation on a cold cache, not from the ML. Around 2.5 GB is a realistic ceiling, so 4 GB is comfortable and 8 GB is plenty.
CPU is what actually limits throughput. dlib is entirely CPU-bound, so a shared or burstable vCPU processes photos noticeably slower than the core count suggests.
Storage grows quickly: uploads are kept at full resolution, alongside cropped face images and a thumbnail cache.
Repository · Releases · Issues · MIT licensed
Getting started
Reference
Going further