ZITADEL Docs
Deploy & OperateSelf-Hosted

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 --wait

That'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.com

Enable TLS

Pick a TLS mode and download the matching overlay file:

ModeOverlay fileWhen to use
Let's Encryptdocker-compose.mode-letsencrypt.ymlPublic domain, automatic certs
External TLSdocker-compose.mode-external-tls.ymlBehind a load balancer, CDN, or WAF that terminates TLS
Local TLSdocker-compose.mode-local-tls.ymlSelf-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 --wait

Set 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)" >> .env

External URL settings

These three settings must match your public endpoint exactly:

SettingMeaningExample
ZITADEL_DOMAINThe public domain users type inauth.example.com
ZITADEL_EXTERNALPORTThe port visible to users443 for HTTPS
ZITADEL_EXTERNALSECUREWhether the public URL uses HTTPStrue 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=redis

Then start with the cache profile:

docker compose --env-file .env -f docker-compose.yml --profile cache up -d --wait

Stage 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 --wait

This creates three ZITADEL containers:

  1. zitadel-init — runs database migrations (one-shot)
  2. zitadel-setup — configures the instance (one-shot)
  3. zitadel-api — starts the API server (long-running)

Enable observability

Set in .env:

ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_TYPE=grpc

Download 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 --wait

Traces 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=3

Updating 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 --wait

ZITADEL_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?

On this page