Running with Docker
Docker is the supported deployment path. The image bundles the OpenCV and Pillow system libraries that are tedious to install by hand.
Quick start
git clone https://github.com/fabriziosalmi/brandkit.git
cd brandkit
docker compose up -d --buildBrandKit is now on http://localhost:8000.
docker compose logs -f brandkit # follow logs
docker compose down # stop
docker compose down -v # stop and drop volumesWhat the compose file does
services:
brandkit:
build: .
container_name: brandkit
ports:
- "8000:8000"
volumes:
- ./static/uploads:/app/static/uploads
environment:
- FLASK_ENV=production # legacy, no longer does anything
restart: unless-stoppedThree things worth knowing:
- The bind mount is a bind mount, not a named volume.
./static/uploadson your host is the app's upload directory. Everything anyone generates lands in your working copy. Add it to.gitignore(it already is) and read Privacy. FLASK_ENV=productionis now a no-op and can be dropped. It used to gate the cleanup thread; cleanup is controlled by theBRANDKIT_CLEANUP_*variables and runs under gunicorn regardless.- The port is fixed at 8000 inside the container. Change the left-hand side only:
"127.0.0.1:9000:8000"to bind on a different host port and stop exposing it on every interface.
Running without Compose
docker build -t brandkit .
docker run -d \
--name brandkit \
-p 127.0.0.1:8000:8000 \
-v "$(pwd)/static/uploads:/app/static/uploads" \
-v "$HOME/.u2net:/root/.u2net" \
-e BRANDKIT_MAX_UPLOAD_MB=32 \
--restart unless-stopped \
brandkitThe second volume caches rembg's ONNX models on the host, so rebuilding the image does not re-download ~180 MB.
The image
Dockerfile starts from python:3.11-slim and installs the native dependencies for rembg and OpenCV (libgl1, libglib2.0-0, libjpeg-dev, zlib1g-dev, libpng-dev, libwebp-dev, and the OpenCV core/imgproc headers). It then installs rembg, onnxruntime, opencv-python-headless and numpy, followed by everything in requirements.txt.
Expect a build of several minutes and a final image around 2 GB. That is the cost of shipping ONNX Runtime and the scientific Python stack.
The entrypoint
CMD ["bash", "entrypoint.sh"]. The script picks a server in this order:
- gunicorn, if it is on
PATH—gunicorn --bind 0.0.0.0:$PORT --worker-tmp-dir /dev/shm app:app flask run, if the CLI is present andFLASK_APPis set- a Python fallback that imports
app.pyand callsapp.run(host="0.0.0.0")
It also creates static/uploads if it is missing and honours PORT (default 8000).
Set a secret key before adding workers
The entrypoint does not pass --workers, so gunicorn's default of 1 applies. If you raise it, set BRANDKIT_SECRET_KEY first — without it each worker generates its own signing key and CSRF tokens minted by one worker are rejected by the next.
With several workers you probably also want BRANDKIT_CLEANUP_ENABLED=false on all but one, so a single process owns the file sweep.
Updating
cd brandkit
git pull
docker compose up -d --buildUploads in the bind mount survive. If you changed config.json, your changes survive too — it is copied into the image at build time but you are running a fresh copy from the repo.
Health check
There is no dedicated /healthz endpoint. Use the root page or the format catalogue:
healthcheck:
test: ["CMD", "python", "-c",
"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/format-info').status==200 else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s/format-info is a GET, needs no CSRF token, does no image work, and returns JSON — a good liveness probe.
Resource limits
Background removal on a large image can allocate a lot of memory. If you are running on a small VPS, cap it explicitly:
deploy:
resources:
limits:
memory: 4Gand lower the upload ceiling with BRANDKIT_MAX_UPLOAD_MB. See Environment variables.