Boreas is a FastAPI background-removal backend built for a narrow job: accept uploads quickly, push heavy work off-request, and stay understandable under load.
Architecture rationale lives in docs/system-design.md. Contributor rules live in CONTRIBUTING.md. Deployment and model-tuning guidance lives in docs/deployment-guide.md. Developer integration guidance lives in docs/integration-guide.md.
POST /v1/media/jobsaccepts an image and returns a job id immediately.GET /v1/media/jobs/{job_id}returns job state and a result URL when ready.GET /v1/media/jobs/{job_id}/streamstreams job updates through SSE./exposes a lightweight reachability endpoint for liveness checks./healthexposes public runtime health, queue depth, and staging metrics.
app/features/media: public upload API, validation, staging, normalization, ingest workerapp/features/rembg: background-removal compute and compute workerapp/features/health: status and health endpointsapp/core: bootstrap, middleware, config, Redis, ARQ, shared storage primitives
The public request path stays thin. The queue carries job ids, not images. Redis stages uploads briefly, object storage holds prepared sources and final results, and the workers own the expensive work.
- the API should return fast even when processing is expensive
- input normalization and background removal are different responsibilities
- Redis is good for short-lived state handoff, not long-lived file storage
- object lifecycle has to be explicit or it turns into cost and cleanup drift
- contributor ownership should be obvious from the folder layout
The longer explanation is in docs/system-design.md.
By default Boreas allows 5/minute per client IP for API reads and uploads.
That is intentionally conservative because uploads and rembg work are expensive. Both limits are environment-configurable through API_RATE_LIMIT and UPLOAD_RATE_LIMIT.
If clients want frequent job updates, SSE is the intended path. Repeated polling will hit the limit faster and costs more.
- staged uploads expire from Redis quickly
- job metadata expires through Redis TTL
- prepared source objects should be deleted immediately after successful compute
- final result objects should expire after one hour through an object storage lifecycle rule
The app assumes the bucket lifecycle matches the one-hour result retention policy.
Copy .env.example to .env and set the required values.
Important settings:
REDIS_URLAPI_RATE_LIMITUPLOAD_RATE_LIMITJOB_TTL_SECONDSRESULT_URL_TTL_SECONDSMEDIA_SOURCE_MAX_BYTESMEDIA_STAGING_TTL_SECONDSMEDIA_WORKERSBACKGROUND_REMOVAL_WORKERSSTORAGE_ENDPOINT_URLSTORAGE_ACCESS_KEY_IDSTORAGE_SECRET_ACCESS_KEYSTORAGE_BUCKET_NAMEREMBG_MODELREMBG_POST_PROCESS_MASKREMBG_ALPHA_MATTINGREMBG_ALPHA_MATTING_FOREGROUND_THRESHOLDREMBG_ALPHA_MATTING_BACKGROUND_THRESHOLDREMBG_ALPHA_MATTING_ERODE_SIZEREMBG_OMP_NUM_THREADSSTARTUP_DEPENDENCY_MAX_ATTEMPTSSTARTUP_DEPENDENCY_RETRY_DELAY_SECONDSLOG_LEVELLOGFIRE_SEND_TO_LOGFIRELOGFIRE_TOKENLOGFIRE_SERVICE_NAMELOGFIRE_ENVIRONMENT
If LOGFIRE_SEND_TO_LOGFIRE=false, Boreas keeps logs local even when Logfire is installed.
LOGFIRE_ENVIRONMENT is optional. Set it only if you want Logfire to split or filter telemetry by deployment environment. Leaving it empty keeps all Boreas telemetry under the same service without an extra environment dimension.
STARTUP_DEPENDENCY_MAX_ATTEMPTS and STARTUP_DEPENDENCY_RETRY_DELAY_SECONDS control how long Boreas retries Redis and ARQ warmup before failing startup.
For a small CPU-only VPS, keep the deployment conservative:
- keep
MEDIA_WORKERS=1 - keep
BACKGROUND_REMOVAL_WORKERS=1 - use
REMBG_MODEL=isnet-general-usefor a better edge-quality and latency balance thanu2netp - keep
REMBG_POST_PROCESS_MASK=true - leave
REMBG_ALPHA_MATTING=falseby default and only enable it if you can afford lower throughput for finer edges like hair or fur - set
REMBG_OMP_NUM_THREADS=2so ONNX does not oversubscribe the VM
If Coolify lets you mount persistent storage, mount U2NET_HOME so model downloads survive redeploys instead of re-downloading on the next cold worker start.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
./start.shUse .vscode/launch.json for local debugging.
It includes:
Boreas APIBoreas Media WorkerBoreas Background Removal WorkerBoreas API + Workerscompound launch
That gives you a clean way to debug the API and both worker processes without relying on the shell script.
Run the current v1 test surface with:
./.venv/bin/python -m unittest discover -s testsThe suite currently covers:
- ingest lifecycle behavior
- source deletion after compute
- one-hour result URL assumptions
- health metrics payload
