Back to Zitadel

Set up ZITADEL with Docker Compose

apps/docs/content/self-hosting/deploy/compose.mdx

5.0.0-base7.0 KB
Original Source

import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx'; import DefaultUser from './_defaultuser.mdx' import Next from './_next.mdx'

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:

shell
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.

<DefaultUser components={props.components} /> <Callout type="info"> The base stack runs: **Traefik** ([reverse proxy](/self-hosting/manage/reverseproxy/reverse_proxy)) → **ZITADEL API** (Go) + **ZITADEL Login** (Next.js) → **PostgreSQL**. All routing, including [gRPC over HTTP/2](/self-hosting/manage/http2), is handled automatically by Traefik — no extra configuration needed. </Callout>

Stage 2 — Homelab / Semi-Production

Use your own domain

Edit .env and set your real domain (see Custom Domains for details):

dotenv
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:

shell
# 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

<Callout type="warn"> The masterkey [encrypts sensitive data at rest](/concepts/architecture/secrets). 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. </Callout>
shell
# 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.

<NoteInstanceNotFound components={props.components} />

Enable caching with Redis

See Cache Configuration for all available options. Add these to .env:

dotenv
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:

shell
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:

shell
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:

dotenv
ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_TYPE=grpc

Download the collector configuration and start with the observability profile:

shell
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

<Callout type="info"> Scaling requires the **prodlike overlay** so that migrations run once in `zitadel-init` instead of on every replica. </Callout>
shell
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:

shell
docker compose --env-file .env -f docker-compose.yml pull
docker compose --env-file .env -f docker-compose.yml up -d --wait
<Callout> `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. </Callout>

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.

<Next components={props.components} />