Set up ZITADEL with Docker Compose
This guide takes you from zero to a running ZITADEL instance in minutes and then shows you how to harden it for a homelab or semi-production deployment.
Prerequisites
- Docker Engine 24+ with the Compose plugin (
docker compose) - A machine with at least 2 GB RAM
See Requirements for supported database, cache, and proxy versions.
Stage 1 — Quickstart (2 minutes)
Download the two required files, copy the example config, and start:
mkdir zitadel-compose && cd zitadel-compose
# Download the compose file and example environment
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.yml &&
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/.env.example
# Create your environment file and start
cp .env.example .env
docker compose up -d --waitThat's it. Visit http://localhost:8080 to open the login screen.
Visit http://localhost:8080/ui/console?login_hint=zitadel-admin@zitadel.localhost and enter Password1! to log in.
The base stack runs: Traefik (reverse proxy) → ZITADEL API (Go) + ZITADEL Login (Next.js) → PostgreSQL. All routing, including gRPC over HTTP/2, is handled automatically by Traefik — no extra configuration needed.
Stage 2 — Homelab / Semi-Production
Use your own domain
Edit .env and set your real domain (see Custom Domains for details):
ZITADEL_DOMAIN=auth.example.comEnable TLS
Pick a TLS mode and download the matching overlay file:
| Mode | Overlay file | When to use |
|---|---|---|
| Let's Encrypt | docker-compose.mode-letsencrypt.yml | Public domain, automatic certs |
| External TLS | docker-compose.mode-external-tls.yml | Behind a load balancer, CDN, or WAF that terminates TLS |
| Local TLS | docker-compose.mode-local-tls.yml | Self-signed certs for LAN-only access |
Download the overlay you need, then start with both files:
# Download the overlay (replace with your chosen mode)
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-letsencrypt.yml
# Start with the overlay
docker compose --env-file .env \
-f docker-compose.yml \
-f docker-compose.mode-letsencrypt.yml \
up -d --waitSet LETSENCRYPT_EMAIL in .env to receive certificate expiry notifications.
Harden secrets
The masterkey encrypts sensitive data at rest. Once ZITADEL has been initialized with a masterkey, it cannot be changed without losing access to encrypted data. Generate it before first start and store it safely.
# Generate a secure masterkey (must be exactly 32 characters)
ZITADEL_MASTERKEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)
echo "ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY" >> .env
# Set strong database passwords
echo "POSTGRES_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" >> .env
echo "POSTGRES_ZITADEL_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" >> .envExternal URL settings
These three settings must match your public endpoint exactly:
| Setting | Meaning | Example |
|---|---|---|
ZITADEL_DOMAIN | The public domain users type in | auth.example.com |
ZITADEL_EXTERNALPORT | The port visible to users | 443 for HTTPS |
ZITADEL_EXTERNALSECURE | Whether the public URL uses HTTPS | true for any TLS mode |
If these don't match reality, ZITADEL returns "Instance not found" errors. This is the most common deployment issue — see TLS Modes for details.
This guide is based on a local setup. If you encounter an error "Instance Not Found" please read the following section: Instance not found
Enable caching with Redis
See Cache Configuration for all available options. Add these to .env:
ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED=true
ZITADEL_CACHES_INSTANCE_CONNECTOR=redis
ZITADEL_CACHES_MILESTONES_CONNECTOR=redis
ZITADEL_CACHES_ORGANIZATION_CONNECTOR=redisThen start with the cache profile:
docker compose --env-file .env -f docker-compose.yml --profile cache up -d --waitStage 3 — Beyond Compose
Production-like init/setup/start split
For controlled upgrades, separate database initialization from the running API:
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.prodlike.yml &&
docker compose --env-file .env \
-f docker-compose.yml \
-f docker-compose.prodlike.yml \
up -d --waitThis creates three ZITADEL containers:
zitadel-init— runs database migrations (one-shot)zitadel-setup— configures the instance (one-shot)zitadel-api— starts the API server (long-running)
Enable observability
Set in .env:
ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_TYPE=grpcDownload the collector configuration and start with the observability profile:
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/otel-collector-config.yaml &&
docker compose --env-file .env -f docker-compose.yml --profile observability up -d --waitTraces are logged to the collector's stdout by default (docker compose logs otel-collector).
To forward traces to your own backend (Grafana Tempo, Jaeger, OpenObserve, etc.), set OTEL_BACKEND_ENDPOINT in .env and uncomment the otlp exporter in otel-collector-config.yaml.
See Metrics for Prometheus scraping and available metric types.
Scale API replicas
Scaling requires the prodlike overlay so that migrations run once in zitadel-init instead of on every replica.
docker compose --env-file .env \
-f docker-compose.yml \
-f docker-compose.prodlike.yml \
up -d --scale zitadel-api=3Updating ZITADEL
Edit ZITADEL_VERSION in .env, then:
docker compose --env-file .env -f docker-compose.yml pull
docker compose --env-file .env -f docker-compose.yml up -d --waitZITADEL_FIRSTINSTANCE_* and ZITADEL_DEFAULTINSTANCE_* environment variables are only applied during the initial setup.
To change settings on an existing installation, use the Admin Console or Admin API.
Moving to Kubernetes
Docker Compose is ideal for getting started and homelab deployments. For production workloads, review the Production Checklist and deploy with the official Helm chart for Kubernetes.
The compose pack and the Helm chart share the same application configuration model (ZITADEL_* environment variables), so migration is straightforward.
What's next
For running a production grade ZITADEL instance in your environment, go on with the configure ZITADEL section.
The ZITADEL management console requires end-to-end HTTP/2 support
Was this page helpful?