Deployment
Cropwright ships as a single Docker image: SvelteKit's static adapter build served by nginx.
- Non-root: the image runs nginx as uid 101, listening on container
port
8080. Compose mapsOP_CROPWRIGHT_PORT(default5184) to it. - Docs on the same origin: nginx proxies the API's Swagger UI (
/docs), ReDoc (/redoc) and/openapi.json, and serves the one documentation site at/OpenProcessor/when thedocsservice ofdocker-compose.docs.ymlis running. PointDOCS_UPSTREAMelsewhere to host the docs yourself; if the docs container is down, only/OpenProcessor/answers 502. - One compose project: the
cropwrightservice is part of the OpenProcessor compose project and shares its network, so nginx proxiesPUBLIC_API_PREFIX/*to the API by service name (op-api) — no CORS, and the browser never sees the backend's real hostname. - No database, no persistent volumes: Cropwright itself is stateless. Everything it shows comes from the backend on each request.
One deployment, many projects
A single Cropwright container and a single backend API serve every
project. Projects are separated by the URL (/p/<project>/...) and by the
prefix the backend serves for each; you do not run a container per project.
The one API prefix and one upstream in your .env cover them all. See
Projects for creating, pausing, archiving and
deleting them, and Project administration
for the operator view.
Reverse proxy behavior
nginx proxies PUBLIC_API_PREFIX/* to API_UPSTREAM, with three
differences worth knowing when you put another proxy in front:
- The general API location has a 120 s read timeout.
- Ingest routes (
.../projects/<slug>/ingest/...) get a 600 s read timeout and theOP_CROPWRIGHT_INGEST_MAX_REQUEST_MBbody cap, because a batch with detection and embedding can run long. - The dataset-archive upload route (
.../projects/<slug>/datasets/uploads) gets a one-hour read timeout and theOP_CROPWRIGHT_DATASET_UPLOAD_MAX_MBbody cap.
An authenticating proxy in front of Cropwright must pass these through unchanged, or large uploads will fail with a 413 or a gateway timeout.
Network exposure
The compose file publishes on ${OP_UI_BIND_ADDRESS:-${OP_BIND_ADDRESS:-127.0.0.1}}:${OP_CROPWRIGHT_PORT:-5184},
so by default Cropwright answers on this machine only. Set OP_UI_BIND_ADDRESS=0.0.0.0 (or a LAN
address) to let your local network open it; that also opens the optional monitoring UIs, and the API
port stays on OP_BIND_ADDRESS.
See Security: the API has no authentication.
Production Dockerfile conventions
Multi-stage build (Node build stage discarded, nginx:alpine-family
runtime), non-root user, healthcheck against 127.0.0.1:8080/ (not
localhost — that resolves to ::1 and nginx only listens on IPv4 inside
the container).
Registry
Released images are published to Docker Hub as davidamacey/cropwright
(X.Y.Z, X.Y and latest), multi-arch for linux/amd64 and
linux/arm64. docker-compose.yml only pulls; set OP_IMAGE_TAG (or a full OP_CROPWRIGHT_IMAGE
reference) to pin a version. To build from the checkout instead, add the dev overlay:
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile cropwright up -d --build cropwright.