Back to Zitadel

ZITADEL Docker Compose — Developer Reference

deploy/compose/README.md

5.0.0-base5.6 KB
Original Source

ZITADEL Docker Compose — Developer Reference

User-facing documentation: zitadel.com/docs/self-hosting/deploy/compose

This README is the contributor/developer reference — architecture decisions, file conventions, and routing logic.

Architecture

                 ┌─────────────────────────┐
  Browser ──────►│  Traefik (proxy)        │
                 │  Port 80 / 443          │
                 └───┬──────────┬──────────┘
                     │          │
          ┌──────────▼──┐  ┌───▼──────────┐
          │ zitadel-api  │  │ zitadel-login │
          │ Go :8080     │  │ Next.js :3000 │
          └──────┬───────┘  └──────────────┘
                 │
          ┌──────▼───────┐
          │  PostgreSQL   │
          └──────────────┘

Optional services via profiles: redis (cache), otel-collector (observability).

File Conventions

FileRoleNotes
docker-compose.ymlBase stack — all modes start from thisMust work standalone with .env.example
docker-compose.mode-letsencrypt.ymlTLS overlay: ACME HTTP challengeDeclares its own letsencrypt volume
docker-compose.mode-external-tls.ymlTLS overlay: upstream LB terminates TLSEnables forwarded headers
docker-compose.mode-local-tls.ymlTLS overlay: self-signed certsMounts ./certs/ and traefik-local-tls.yml
docker-compose.prodlike.ymlInit/setup/start splitUses YAML anchors for shared DB env
docker-compose.test.ymlCI smoke test overlayOverrides images to :local tags
.env.exampleUser-facing config templateCopy to .env before first run
.env.testCI-only configUsed by NX targets: test-run, test-e2e, test-full, stop
otel-collector-config.yamlOTEL Collector pipeline configLogs traces to stdout; configure OTEL_BACKEND_ENDPOINT to forward to a backend
traefik-local-tls.ymlTraefik dynamic config for local certsReferenced by local-tls overlay
project.jsonNX project definitionTargets: test-config, test-run, test-e2e, test, test-full, stop
AGENTS.mdAI agent instructions for this directory

Routing Rules

Traefik routes all traffic for ${ZITADEL_DOMAIN} via Docker labels:

PriorityRuleTargetMiddleware
400Path(/)zitadel-loginreplacepath=/ui/v2/login/
250PathPrefix(/ui/v2/login)zitadel-login
200PathPrefix(/api)zitadel-apistripprefix=/api
100Everything else (OIDC, SAML, gRPC, gRPC-web, API v2 REST, ...)zitadel-api (h2c)

No dedicated gRPC router is needed: Traefik's h2c backend scheme forwards gRPC and gRPC-web transparently. API v2 is served as REST/JSON via the gRPC-gateway at /v2/... paths.

Both web (HTTP) and websecure (HTTPS) entrypoints have identical router sets.

Why this routing model

  • /api alias exists for DX — tools can use https://auth.example.com/api/...
  • Canonical paths (e.g., /.well-known/openid-configuration, /oauth/v2/...) must remain at root for OIDC/SAML protocol compliance
  • gRPC, gRPC-web, and REST all share the catch-all router — no separate gRPC router is needed because the h2c backend scheme makes Traefik forward all protocols transparently over HTTP/2

External Settings Invariant

ZITADEL_EXTERNALDOMAIN, ZITADEL_EXTERNALPORT, and ZITADEL_EXTERNALSECURE must match the public URL that users see. If they don't, ZITADEL returns "Instance not found" errors. This is the single most common deployment issue.

Testing

Local NX targets for testing the compose stack:

TargetWhat it doesRequires Docker?
test-configValidates all overlay combinations parse with docker compose configNo (just the CLI)
test-runBuilds local images (@zitadel/api:pack + @zitadel/login:pack), starts the stack with docker compose up --waitYes
test-e2eRuns the full Playwright suite (wiring.spec.ts + smoke.spec.ts) against localhost:8888 through Traefik: per-service wiring checks (login, console, OIDC, SAML, API v1 REST, gRPC h2c, gRPC-web, API v2 REST HTTP/1.1 + HTTP/2) and the browser login flowYes (stack must be running)
testLightweight — delegates to test-config only. Safe for nx affectedNo
test-fullFull pipeline: test-configtest-run → Playwright wiring + browser tests → teardownYes
stopTears down the zitadel-compose-test stack and removes volumesYes

Rejected Alternatives

AlternativeWhy rejected
Strict /api-only rewriteBreaks canonical protocol paths (OIDC, SAML)
/grpc path-prefix routinggRPC clients/tools don't use path prefixes
Dedicated gRPC router (HeaderRegexp(Content-Type, ^application/grpc.*))Redundant: h2c backend handles gRPC, gRPC-web, and Connect-RPC natively; the catch-all covers all three
Single container (API + Login)Not aligned with v4 architecture; Login is a separate Next.js process
Merged TLS configsEach TLS mode must remain independently composable
network_mode: service: for LoginFragile, port conflicts, doesn't work with Traefik routing