Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/zitadel/zitadel/llms.txt

Use this file to discover all available pages before exploring further.

Deploy ZITADEL using Docker Compose for development, homelab, or single-node production environments.

Architecture

The Docker Compose deployment consists of four core services:
  • zitadel-api: Go backend (port 8080)
  • zitadel-login: Next.js login UI (port 3000)
  • postgres: PostgreSQL database
  • proxy: Traefik reverse proxy (ports 80/443)
Optional services:
  • redis: Cache connector (enabled with --profile cache)
  • otel-collector: Observability (enabled with --profile observability)

Quick Start

1

Create environment file

Copy the example environment file and configure your domain:
curl -o .env https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/.env.example
Edit .env and update:
.env
ZITADEL_DOMAIN=localhost
ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters
The masterkey must be exactly 32 characters. Generate a secure key for production:
tr -dc A-Za-z0-9 </dev/urandom | head -c 32
2

Download Docker Compose file

curl -o docker-compose.yml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.yml
3

Start ZITADEL

docker compose up -d --wait
The --wait flag ensures all services are healthy before returning.
4

Access ZITADEL

Open your browser to:
http://localhost:8080
Default admin credentials:
  • Username: zitadel-admin
  • Password: Set during first login

TLS Modes

ZITADEL supports four TLS deployment modes via Docker Compose overlays.

Local Development (No TLS)

Default mode for local development:
docker compose up -d --wait
Configuration:
.env
ZITADEL_DOMAIN=localhost
ZITADEL_EXTERNALPORT=8080
ZITADEL_EXTERNALSECURE=false
PROXY_HTTP_PUBLISHED_PORT=8080

Let’s Encrypt (Automatic TLS)

Automatic TLS certificates via ACME HTTP challenge:
curl -o docker-compose.mode-letsencrypt.yml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-letsencrypt.yml

docker compose \
  -f docker-compose.yml \
  -f docker-compose.mode-letsencrypt.yml \
  up -d --wait
Configuration:
.env
ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=true
LETSENCRYPT_EMAIL=ops@example.com
Ensure ports 80 and 443 are accessible from the internet for the ACME challenge.

External TLS Termination

For deployments behind an upstream load balancer or reverse proxy:
curl -o docker-compose.mode-external-tls.yml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-external-tls.yml

docker compose \
  -f docker-compose.yml \
  -f docker-compose.mode-external-tls.yml \
  up -d --wait
Configuration:
.env
ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=true
# Trusted proxy IPs (CIDR notation)
TRAEFIK_TRUSTED_IPS=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
Configure TRAEFIK_TRUSTED_IPS to include only your upstream proxy’s IP ranges to prevent header spoofing.

Local TLS (Self-Signed)

For testing TLS locally with self-signed certificates:
curl -o docker-compose.mode-local-tls.yml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-local-tls.yml
curl -o traefik-local-tls.yml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/traefik-local-tls.yml

mkdir -p certs
openssl req -x509 -newkey rsa:4096 -keyout certs/key.pem -out certs/cert.pem \
  -days 365 -nodes -subj "/CN=localhost"

docker compose \
  -f docker-compose.yml \
  -f docker-compose.mode-local-tls.yml \
  up -d --wait
Configuration:
.env
ZITADEL_DOMAIN=localhost
ZITADEL_EXTERNALPORT=8443
ZITADEL_EXTERNALSECURE=true

Routing Configuration

Traefik routes traffic based on path prefixes:
PriorityPathServicePurpose
400/zitadel-loginRoot redirect to login UI
250/ui/v2/loginzitadel-loginLogin UI and assets
200/apizitadel-apiAPI alias (stripped to root)
100Everything elsezitadel-apiOIDC, SAML, gRPC, API v2
The /api prefix is a convenience alias. Canonical protocol paths remain at root:
  • /.well-known/openid-configuration
  • /oauth/v2/authorize
  • /v2/... (API v2 REST endpoints)
gRPC and gRPC-Web traffic is automatically handled by Traefik’s h2c backend configuration. No special path routing is needed.

Production-Like Setup

For production environments, use separate init, setup, and start phases:
curl -o docker-compose.prodlike.yml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.prodlike.yml

docker compose \
  -f docker-compose.yml \
  -f docker-compose.prodlike.yml \
  up -d --wait
This overlay:
  • Runs zitadel init as a separate one-shot container
  • Runs zitadel setup as a separate one-shot container
  • Starts zitadel-api with start command only
Benefits:
  • Clearer separation of initialization vs. runtime
  • Easier to manage database migrations
  • Better suited for GitOps workflows

Optional Features

Redis Cache

Enable Redis for distributed caching:
docker compose --profile cache up -d redis
Update .env:
.env
ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED=true
ZITADEL_CACHES_CONNECTORS_REDIS_ADDR=redis:6379
ZITADEL_CACHES_INSTANCE_CONNECTOR=redis
ZITADEL_CACHES_MILESTONES_CONNECTOR=redis
ZITADEL_CACHES_ORGANIZATION_CONNECTOR=redis
Restart ZITADEL:
docker compose restart zitadel-api

OpenTelemetry Observability

Enable tracing with OpenTelemetry:
curl -o otel-collector-config.yaml https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/otel-collector-config.yaml

docker compose --profile observability up -d otel-collector
Update .env:
.env
ZITADEL_INSTRUMENTATION_SERVICENAME=zitadel-api
ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_TYPE=grpc
ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_ENDPOINT=otel-collector:4317
ZITADEL_INSTRUMENTATION_TRACE_EXPORTER_INSECURE=true
Restart ZITADEL:
docker compose restart zitadel-api

Common Issues

Instance Not Found Error

The most common issue is mismatched external domain configuration.
ZITADEL_EXTERNALDOMAIN, ZITADEL_EXTERNALPORT, and ZITADEL_EXTERNALSECURE must exactly match the URL users access in their browser.
If users access https://auth.example.com:443, configure:
.env
ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=true
If users access http://localhost:8080, configure:
.env
ZITADEL_DOMAIN=localhost
ZITADEL_EXTERNALPORT=8080
ZITADEL_EXTERNALSECURE=false

Database Connection Issues

Check PostgreSQL health:
docker compose ps postgres
docker compose logs postgres
Verify database credentials in .env:
.env
POSTGRES_DB=zitadel
POSTGRES_ADMIN_USER=postgres
POSTGRES_ADMIN_PASSWORD=postgres
POSTGRES_ZITADEL_USER=zitadel
POSTGRES_ZITADEL_PASSWORD=zitadel

Upgrading

1

Update image version

Edit .env:
.env
ZITADEL_VERSION=v4.11.0
2

Pull new images

docker compose pull
3

Restart services

docker compose up -d --wait
ZITADEL automatically applies database migrations on startup.

Maintenance

View Logs

# All services
docker compose logs -f

# Specific service
docker compose logs -f zitadel-api

Backup Database

docker compose exec postgres pg_dump -U postgres zitadel > backup.sql

Stop Services

# Stop but preserve volumes
docker compose down

# Stop and remove volumes (data loss!)
docker compose down -v

Next Steps