Deploy with Docker + Caddy
A production deploy uses three pieces: the server image, a .env file, and caddy-docker-proxy labels on the external caddy network.
- docker-compose.yml
- .env (copy from .env.production)
- Dockerfile
- Makefile
Copy .env.example to .env and fill in the secrets. The Makefile’s make secret-gen target generates OAUTH_SECRET_KEY for you if it’s missing or blank (idempotent — re-runs preserve the existing key).
# Required — server refuses to start without theseOAUTH_SECRET_KEY= # `make secret-gen` fills this inADMIN_PASSWORD= # no default; RuntimeError if unset (see below)DOMAIN=mcp.example.comCOMPOSE_PROJECT_NAME=blender-mcp
# Optional — demo user is opt-inDEMO_PASSWORD= # only set this if you want a `demo` account createddocker-compose.yml
Section titled “docker-compose.yml”services: blender-mcp: build: . container_name: blender-mcp restart: unless-stopped env_file: .env volumes: - ./data:/data networks: - caddy - internal labels: caddy: ${DOMAIN} caddy.reverse_proxy: "{{upstreams 8000}}" caddy.tls.import: rfc2136_local
networks: caddy: external: true internal: driver: bridgeThe caddy.tls.import: rfc2136_local line pulls in a Caddy snippet that uses RFC 2136 dynamic DNS-01 challenges. Drop the line if you’re using HTTP-01.
Bring it up
Section titled “Bring it up”-
Generate the secret + set the admin password:
Terminal window make secret-gen # fills OAUTH_SECRET_KEY in .env if blank$EDITOR .env # set ADMIN_PASSWORD (required) -
Start the production stack:
Terminal window make prod # builds image, brings up compose with caddy labels -
Watch the logs:
Terminal window make logs # or: docker compose logs -fExpected first lines:
blender-mcp | INFO uvicorn:Started server process [1]blender-mcp | INFO uvicorn:Uvicorn running on http://0.0.0.0:8000 -
Verify TLS from another host (the Makefile has a target for this):
Terminal window make health # GETs https://${DOMAIN}/health, pretty-prints JSON# or directly:curl -fsS https://${DOMAIN}/health
Makefile targets
Section titled “Makefile targets”The root Makefile has 14 targets. make help renders them all:
| Target | Purpose |
|---|---|
prod | Start the production stack (FastMCP server behind caddy-docker-proxy) |
dev | Start the dev stack with hot reload |
down | Stop both stacks (prod + dev) |
logs | Tail logs from whichever stack is running |
restart | Graceful restart without rebuild |
build | Build the production image without starting it |
rebuild | Full no-cache rebuild of prod image |
ps | Show running services |
shell | Open an interactive bash shell in the running prod container |
caddy-reload | Force the host Caddy to re-read its config |
secret-gen | Generate OAUTH_SECRET_KEY into .env only if missing/blank |
health | Hit the public /health endpoint and pretty-print the response |
clean | Stop everything and remove project volumes |
help | Show all targets |
Environment variables
Section titled “Environment variables”| Variable | Required? | Purpose |
|---|---|---|
OAUTH_SECRET_KEY | yes | HMAC signing key for issued tokens. Generate with make secret-gen. |
ADMIN_PASSWORD | yes | Admin user’s password. No default — server refuses to start without it. |
DOMAIN | yes | Public hostname Caddy serves from. |
COMPOSE_PROJECT_NAME | yes | Compose project namespace. |
DEMO_PASSWORD | no | When set, creates a demo user with this password. Leave unset in production. |
Hot-reload in dev
Section titled “Hot-reload in dev”make dev # docker compose up with the dev profile + source mounts + --reloadThat target runs uv run uvicorn blender_mcp.oauth_server:app --reload --host 0.0.0.0 inside the container with source volumes mounted from the host.
Live reference deploy
Section titled “Live reference deploy”The branch’s reference deployment runs at https://mcp.l.warehack.ing/mcp/ (note the trailing slash — see Quickstart for why the addon auto-adds it). If you want to skip standing up your own server while exploring the API, you can authenticate against this deployment with the demo credentials and point clients at it directly.
Operational notes
Section titled “Operational notes”- The bus state is in-memory only — restarting the container disconnects every persistent client and drops
_pending_jobs. Schedule restarts during quiet windows. See Architecture for the rationale. - caddy-docker-proxy auto-discovers new containers — no manual Caddyfile edits needed.
- Pin the image with
image:rather thanbuild:once you start tagging releases. CalVer (2026.05.25) keeps “when was this built” obvious from the tag.
- OAuth and per-user buses — what the JWT actually carries
- Write your own LLM client — point it at your new deploy