Self-hosted Chutes workspace with:
- an optional public landing page at
/ - OpenWebUI at
/chat/ - n8n at
/n8n/(optional — setDROPZONE_ENABLE_N8N=falsefor chat-only mode) - native Chutes SSO
- Chutes quota/tier/account chrome inside the chat sidebar
- per-service database isolation (compose mode)
- optional shared
e2ee-proxyrouting for OpenAI-compatible traffic
chutes-dropzone keeps the same local-vs-domain deployment model as chutes-n8n-local, but turns it into a single-host AI workspace instead of a single-app install.
This repo supports two packaging shapes:
- a single-container standalone image built from
Dockerfile.local-repo - a Docker Compose deployment driven by
deploy.sh
docker run --rm -it \
--pull always \
--platform linux/amd64 \
-p 443:443 \
ghcr.io/chutesai/chutes-dropzone:latestSee Standalone Image for persisting state, domain mode, reverse proxy setups, and building from source.
For Kubernetes, the easiest supported path is the standalone image with:
INSTALL_MODE=domainCHUTES_TRAFFIC_MODE=directDROPZONE_ENABLE_N8N=false(chat-only — set totrueto include n8n)- one persistent volume mounted at
/data
TLS can be handled two ways:
- Ingress/LB handles TLS: leave
ACME_EMAILempty, expose port 80 only, point your ingress at the service - Caddy manages TLS: set
ACME_EMAIL, expose ports 80 and 443, use aLoadBalancerservice
Example manifest (defaults to chat-only with Caddy-managed TLS):
Typical flow:
- Replace the placeholder secrets and host values in that manifest.
The example defaults to
chat-beta.chutes.aiwithDROPZONE_ENABLE_PUBLIC_LANDING=falseandDROPZONE_ENABLE_N8N=false. Replaceghcr.io/chutesai/chutes-dropzone:<release-tag>with your release tag. - Apply it:
kubectl apply -f examples/kubernetes/standalone-domain-direct.yaml - Point your DNS
A/AAAArecord forDROPZONE_HOSTat the service's external IP.
Important notes:
- Keep this as a single replica. The standalone image stores state under
/data. - When
ACME_EMAILis empty, the standalone edge listens on HTTP only (port 80). Your ingress or load balancer must handle TLS. - When
ACME_EMAILis set, Caddy handles Let's Encrypt automatically. Expose ports 80 and 443 and use aLoadBalancerservice for the simplest setup.
macOS/Linux/WSL:
bash <(curl -fsSL -H 'Cache-Control: no-cache' \
"https://raw.githubusercontent.com/chutesai/chutes-dropzone/main/deploy.sh?$(date +%s)")The deploy script:
- clones or refreshes
chutesai/chutes-dropzone - runs
deploy.sh - auto-clones
sirouk/n8n-nodes-chutesbeside it if missing - resets
n8n-nodes-chutesto the checked-in pinned commit on clean reruns so local builds match CI and release images
When launched from a terminal, the deploy script prompts for install mode and the required Chutes OAuth settings even when invoked via curl ... | bash.
For headless or CI usage, preseed the required environment variables:
curl -fsSL https://raw.githubusercontent.com/chutesai/chutes-dropzone/main/deploy.sh | \
INSTALL_MODE=local \
CHUTES_OAUTH_CLIENT_ID=... \
CHUTES_OAUTH_CLIENT_SECRET=... \
bashManual clone:
git clone https://github.com/chutesai/chutes-dropzone.git
cd chutes-dropzone
./deploy.shIf ../n8n-nodes-chutes is missing, deploy will clone:
https://github.com/sirouk/n8n-nodes-chutes.git
You can override that source if needed with:
CHUTES_N8N_NODES_GIT_URL=git@github.com:sirouk/n8n-nodes-chutes.git ./deploy.shYou can also temporarily test a different branch or commit without changing the checked-in pin:
CHUTES_N8N_NODES_GIT_REF=<branch-or-commit> ./deploy.shAdvanced CI/testing runs can also point deploy at prebuilt local app images and skip the heavy app rebuild:
DROPZONE_N8N_IMAGE=chutes-dropzone-n8n:local \
DROPZONE_OPENWEBUI_IMAGE=chutes-dropzone-openwebui:local \
SKIP_APP_BUILDS=true \
./deploy.sh --forceInteractive deploy asks for:
INSTALL_MODE:localordomainDROPZONE_ENABLE_PUBLIC_LANDING: show landing page at/or redirect to/chat/CHUTES_TRAFFIC_MODE:directore2ee-proxyDROPZONE_HOSTfor domain installs- TLS certificate management (yes for Let's Encrypt via Caddy, no for HTTP-only behind a reverse proxy)
ACME_EMAILif TLS is managed- Chutes OAuth client ID and secret
After deploy:
https://<host>/is either the landing page or a redirect to/chat/, depending onDROPZONE_ENABLE_PUBLIC_LANDINGhttps://<host>/chat/opens OpenWebUIhttps://<host>/n8n/opens n8n (whenDROPZONE_ENABLE_N8N=true)
If you deploy the standalone image on Render or another managed container platform with configurable health checks, set the service health-check path to /health.
That endpoint is served directly by the embedded edge and is a better liveness probe than /static/site.webmanifest, which can briefly fail during app startup while OpenWebUI is still binding its internal upstream.
Register both exact redirect URIs on the same Chutes OAuth app:
https://<host>/oauth/oidc/callbackhttps://<host>/rest/sso/chutes/callback
Recommended scopes:
openidprofilechutes:readchutes:invoke
The public topology is fixed in v1:
/either serves a dark Chutes-branded landing page or redirects to/chat//chat/is the human-friendly OpenWebUI entrypoint and redirects into OpenWebUI's native home route/n8n/reverse-proxies to n8n (returns 404 whenDROPZONE_ENABLE_N8N=false)/v1/*is exposed only whenCHUTES_TRAFFIC_MODE=e2ee-proxy
DROPZONE_ENABLE_PUBLIC_LANDING=true preserves the launcher at /.
DROPZONE_ENABLE_PUBLIC_LANDING=false makes / return a redirect to /chat/, which is the recommended standalone domain deployment shape for chat-beta.chutes.ai.
Local installs intentionally stay on a single exact-cert host instead of subdomains.
local: useshttps://e2ee-local-proxy.chutes.devwith an embedded certificatedomain: uses your realDROPZONE_HOST. TLS is managed by Caddy/ACME whenACME_EMAILis set; otherwise the built-in edge listens on HTTP only for use behind a reverse proxy or ingress
direct: OpenWebUI and n8n use native Chutes endpointse2ee-proxy: OpenWebUI and n8n send OpenAI-compatible LLM traffic to the sharede2ee-proxysidecar, while the public/v1/*edge path simply forwards into that same sidecar. n8n SSO text traffic stays on-proxy, and strict TEE-only model catalogs are enabled by default
See .env.example for the full set. The main operator-facing vars are:
DROPZONE_HOSTDROPZONE_ENABLE_PUBLIC_LANDING— show landing page at/(true) or redirect to/chat/(false)DROPZONE_ENABLE_N8N— include n8n (true, default) or chat-only mode (false)ACME_EMAIL— set for Let's Encrypt TLS; leave empty for HTTP-only (TLS handled upstream)POSTGRES_N8N_USER/POSTGRES_N8N_PASSWORD— per-service database credentials (compose mode)POSTGRES_OPENWEBUI_USER/POSTGRES_OPENWEBUI_PASSWORD— per-service database credentials (compose mode)
Compatibility aliases still exist:
N8N_HOSTmirrorsDROPZONE_HOSTPOSTGRES_DBfalls back toPOSTGRES_N8N_DB
OpenWebUI is kept env-authoritative:
WEBUI_URL=https://<host>/chatOPENID_REDIRECT_URI=https://<host>/oauth/oidc/callbackENABLE_PERSISTENT_CONFIG=falseENABLE_OAUTH_PERSISTENT_CONFIG=falseENABLE_OAUTH_SIGNUP=trueDEFAULT_USER_ROLE=userBYPASS_MODEL_ACCESS_CONTROL=trueENABLE_OAUTH_EMAIL_FALLBACK=trueENABLE_LOGIN_FORM=falseENABLE_PASSWORD_AUTH=falseOAUTH_USERNAME_CLAIM=username
At startup, Dropzone also seeds OpenWebUI runtime config so model backends use the signed-in Chutes OAuth token (system_oauth) for completions, while the global model picker is ordered TEE-first, grouped by provider/lab, and then sorted newest-first within each lab using model version and dated release hints from the Chutes model IDs.
Dropzone keeps that ordering fresh in the background with a server-side sync worker. By default, OpenWebUI refreshes its upstream model cache every 5 minutes and the worker reseeds MODEL_ORDER_LIST on the same cadence, so newly published Chutes models settle into the intended order without a redeploy.
Web search is also enabled by default with OpenWebUI's safe_web loader. Compose deployments run a private SearXNG sidecar with a curated no-key engine set, including DuckDuckGo, Google, Startpage, Reuters, Wikipedia, arXiv, Semantic Scholar, PubMed, GitHub, Stack Overflow, and package/documentation sources. The sync worker keeps the persisted OpenWebUI retrieval config aligned with .env, including conservative defaults for result count, loader concurrency, fetch length, SSL verification, and proxy trust. Operators can still disable it with ENABLE_WEB_SEARCH=false, narrow sources with WEB_SEARCH_DOMAIN_FILTER_LIST, or set WEB_SEARCH_ENGINE=duckduckgo to use OpenWebUI's DDGS backend. Standalone single-container deployments need SEARXNG_QUERY_URL pointed at an external/private SearXNG service for SearXNG; without that, they fall back to DDGS auto with backend retries.
For Chutes models that advertise native tool support, the sync worker also seeds web_search and image_generation into defaultFeatureIds and sets function_calling=native. That makes web and image available to the selected LLM by default without forcing a search or image job on every turn. Operators can set OPENWEBUI_DEFAULT_FEATURE_IDS="" to keep those tools available but off by default, or provide a comma-list such as web_search.
Image generation uses the same native tool path. The chat palette stays clean when the tools menu is collapsed; the image on/off switch and image model selector live inside the tools menu.
When CHUTES_TRAFFIC_MODE=direct, OpenWebUI talks straight to https://llm.chutes.ai/v1 and n8n resolves text traffic against the native Chutes LLM endpoints.
When CHUTES_TRAFFIC_MODE=e2ee-proxy, both OpenWebUI and n8n send invocation traffic through the shared e2ee-proxy sidecar. The sidecar's /v1/models catalog still sources model metadata anonymously from https://llm.chutes.ai/v1/models, then applies local TEE-only filtering when ALLOW_NON_CONFIDENTIAL=false.
Chutes currently does not advertise an email scope or email claim in the live OIDC discovery document, so OpenWebUI uses its synthetic-email fallback for user creation.
Deploy also auto-promotes any existing OAuth-created OpenWebUI users stuck in pending to user, so earlier failed SSO attempts recover cleanly after a redeploy.
Normal runtime is SSO-only. A service account (svc-dropzone@internal.chutes.local) is seeded for internal config sync; it is never exposed to users.
n8n and OpenWebUI both get a generated service account (svc-dropzone@internal.chutes.local) during deploy. Password login is disabled — users authenticate exclusively via Chutes SSO.
To recover OpenWebUI access temporarily, edit .env, set:
ENABLE_OAUTH_SIGNUP=false
ENABLE_LOGIN_FORM=true
ENABLE_PASSWORD_AUTH=truethen restart the stack, use the seeded OpenWebUI admin account, and revert those values afterward.
Dockerfile.local-repo packages:
- n8n
- OpenWebUI
- OpenResty
- Caddy
- the landing page
- bundled
n8n-nodes-chutes - starter workflows
Published image:
ghcr.io/chutesai/chutes-dropzone:latest- release tags are also published as semver tags when releases are cut
Releases are published for linux/amd64.
Build locally:
docker buildx build --load \
-t chutes-dropzone:local-repo \
-f Dockerfile.local-repo .Run interactively:
docker run --rm -it \
-p 80:80 -p 443:443 \
chutes-dropzone:local-repoPersistent standalone state lives under /data:
/data/.n8n/data/openwebui/data/caddy/data/.env
release.sh is the interactive helper for cutting a GitHub release that drives the published GHCR image.
./release.sh --dry-run
./release.sh --version v0.1.0
./release.sh --yesIt:
- proposes the next patch version from existing tags
- prints the pinned refs and image inputs used by CI and release builds
- verifies the checked-in n8n, OpenWebUI, caddy, and e2ee-proxy pins before publishing
- requires a clean worktree before publishing
- uses
ghto create the GitHub release that triggers.github/workflows/release.yml
./scripts/smoke-test.sh --syntax
./scripts/smoke-test.sh
./scripts/e2e-test.shThe smoke and e2e coverage now validate:
- landing page reachability at
/ - OpenWebUI reachability at
/chat/ - n8n reachability at
/n8n/ - native n8n SSO flow
- the fake Chutes IdP OIDC surface used by OpenWebUI
- local proxy
/v1/*behavior in proxy mode - GitHub Actions runs the destructive local E2E suite in both
directande2ee-proxytraffic modes