The doorman for your uploads. A self-hosted service that checks whether an uploaded image is safe to accept — primarily to keep nudity / sexual content off your server. Runs a local LLM via Ollama, needs no GPU, and is a single container.
Send it an image → get a verdict:
{ "allowed": true, "block": false, "confidence": 0.94, "reason": "..." }
Two stages, which is what makes small local models reliable here:
{ allowed, reason, confidence }.Low confidence → uncertain (flag for review). Everything is fail-closed: on
any error, timeout, or overload the answer is "don't accept".
| Tag | Build |
|---|---|
latest, X.Y.Z | CPU-only, slim (~380 MB). Runs anywhere. |
X.Y.Z-gpu | Includes NVIDIA CUDA runners. Run with --gpus all + nvidia-container-toolkit. |
docker run -d --name bouncer --restart unless-stopped \
-p 8080:8080 -v bouncer-models:/root/.ollama \
-e DESCRIBER_MODEL=gemma4:e2b -e DECISION_MODEL=gemma4:e2b \
fkde/bouncer:latest
-v volume persists pulled models across restarts.healthy only once the download is done and it can classify — watch with
docker ps (STATUS column) or curl localhost:8080/healthz.POST /classify.# file upload
curl -s -F [email protected] http://localhost:8080/classify
# or JSON base64
curl -s -X POST http://localhost:8080/classify \
-H 'Content-Type: application/json' \
-d '{"image_base64":"'"$(base64 -i photo.jpg)"'"}'
Integrate fail-closed: accept the upload only when block === false. Every
other case — rejected, uncertain, error, 503 (queue full → retry) — means
do not accept.
| Variable | Default | Meaning |
|---|---|---|
DESCRIBER_MODEL | gemma4:e2b | Stage 1 vision model (must support images). |
DECISION_MODEL | gemma4:e2b | Stage 2 text model. Same tag = fastest. |
THRESHOLD | 0.8 | Below this confidence → uncertain. |
TEMPERATURE | 0 | 0 = most deterministic. |
MAX_DIM | 1536 | Downscale longest edge before inference. |
CLASSIFY_CONCURRENCY | 1 | Images processed at once (CPU: keep 1). |
QUEUE_MAX | 32 | Active+waiting cap before 503 backpressure. |
OLLAMA_URL | internal | Point at an external Ollama (e.g. a GPU host). |
Pull more models anytime:
docker exec bouncer pull-model.sh gemma4:e4b
| Endpoint | Purpose |
|---|---|
POST /classify | Classify an image (multipart image or JSON image_base64). |
GET /healthz | 200 once ready (models pulled), else 503. Used by the healthcheck. |
GET /api/health | Rich status: ready, model count, queue depth. |
GET / | Demo UI. |
docker run -d --name bouncer --restart unless-stopped --gpus all \
-p 8080:8080 -v bouncer-models:/root/.ollama \
-e DESCRIBER_MODEL=gemma4:e2b -e DECISION_MODEL=gemma4:e2b \
fkde/bouncer:latest-gpu
uncertain as "needs a look".Content type
Image
Digest
sha256:1d77be2ab…
Size
126.8 MB
Last updated
2 months ago
docker pull fkde/bouncer