← All articles
SECURITY Zitadel: Cloud-Native Identity and Access Management... 2026-02-09 · zitadel · iam · oidc

Zitadel: Cloud-Native Identity and Access Management You Can Self-Host

Security 2026-02-09 zitadel iam oidc oauth2 authentication identity security

At some point in your self-hosting journey, you'll want single sign-on. You'll want one set of credentials that works across Grafana, Nextcloud, Gitea, and everything else. You'll look at the options and find three names repeated everywhere: Keycloak, Authentik, and Authelia.

Zitadel is the fourth option that fewer people talk about, but it's worth your attention if you want a modern, API-first identity provider that was designed for cloud-native environments from the start. It's written in Go, ships as a single binary, and handles OIDC/OAuth2, SAML, multi-tenancy, and user management without the Java memory overhead of Keycloak or the Python ecosystem of Authentik.

What Zitadel Actually Is

Zitadel is a full identity and access management (IAM) platform. That means it doesn't just sit in front of your reverse proxy and check passwords like Authelia does. It's the source of truth for user identities:

Zitadel vs. Authentik vs. Keycloak vs. Authelia

This comparison matters because these tools occupy different points in the complexity/capability spectrum.

Feature Zitadel Authentik Keycloak Authelia
Type Full IAM Full IAM Full IAM Auth proxy
Language Go Python/Django Java Go
Resource usage ~200 MB RAM ~500 MB RAM ~1 GB+ RAM ~50 MB RAM
Single binary Yes No (multiple services) No (Java + DB) Yes
OIDC/OAuth2 Yes Yes Yes Yes
SAML Yes Yes Yes No
User self-service Yes Yes Yes No
Multi-tenancy Yes (native) Limited (tenants) Yes (realms) No
API gRPC + REST REST REST No admin API
Branding/theming Per-organization Global + per-tenant Per-realm Login portal only
Social login Yes Yes Yes No (via OIDC)
MFA TOTP, WebAuthn, OTP TOTP, WebAuthn, Duo TOTP, WebAuthn TOTP, WebAuthn, Duo
Actions/webhooks Built-in Actions Flows and stages SPI extensions No
Setup complexity Medium Medium High Low
Admin UI Modern, clean Comprehensive Functional (dated) Minimal
Best for API-first, cloud-native Homelab to medium Enterprise Simple SSO

When to pick Zitadel

When to pick alternatives

The honest take: for most homelabs, Authentik is the better choice. It has a larger community, better documentation for homelab use cases, and a more intuitive UI. Zitadel makes more sense when you have API-driven needs, multi-tenancy requirements, or you're building something beyond a personal homelab.

Installation

Docker Compose with PostgreSQL

Zitadel requires PostgreSQL (or CockroachDB for distributed deployments). Here's a minimal setup:

services:
  zitadel:
    image: ghcr.io/zitadel/zitadel:latest
    container_name: zitadel
    command: start-from-init --masterkeyFromEnv --tlsMode disabled
    environment:
      - ZITADEL_DATABASE_POSTGRES_HOST=db
      - ZITADEL_DATABASE_POSTGRES_PORT=5432
      - ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel
      - ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel
      - ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadel-db-password
      - ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable
      - ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=postgres
      - ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=postgres-password
      - ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable
      - ZITADEL_EXTERNALSECURE=false
      - ZITADEL_EXTERNALDOMAIN=zitadel.yourdomain.com
      - ZITADEL_EXTERNALPORT=8080
      - ZITADEL_MASTERKEY=a-random-32-character-string-here
      - ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME=admin
      - ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD=Admin1234!
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    container_name: zitadel-db
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres-password
      - POSTGRES_DB=zitadel
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  db_data:
docker compose up -d

Visit http://your-server:8080 and log in with the admin credentials you configured.

Important: Change the admin password immediately after first login. The ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD is only used for initial setup.

Production considerations

For production use, add TLS. The simplest approach is to put Zitadel behind a reverse proxy (Caddy, Traefik) that handles TLS:

environment:
  - ZITADEL_EXTERNALSECURE=true
  - ZITADEL_EXTERNALDOMAIN=zitadel.yourdomain.com
  - ZITADEL_EXTERNALPORT=443
  - ZITADEL_TLS_ENABLED=false  # Let the reverse proxy handle TLS

Then in your reverse proxy, forward traffic to Zitadel on port 8080. Zitadel uses gRPC-web internally, so make sure your reverse proxy supports it. Caddy handles this natively. For Traefik, you may need to configure gRPC transport.

Configuring OIDC for Your Services

The primary use case: configure your self-hosted services to authenticate against Zitadel using OpenID Connect.

Step 1: Create a project

In Zitadel's console, create a Project (e.g., "Homelab"). Projects group related applications.

Step 2: Create an application

Within the project, create an application for each service:

  1. Click New Application
  2. Choose Web type
  3. Select PKCE or Code authentication method
  4. Set redirect URIs (where the service sends users after login)

For example, configuring Grafana:

Zitadel gives you a Client ID and Client Secret (if not using PKCE).

Step 3: Configure the service

Every OIDC-compatible service needs these endpoints:

Authorization URL: https://zitadel.yourdomain.com/oauth/v2/authorize
Token URL:         https://zitadel.yourdomain.com/oauth/v2/token
Userinfo URL:      https://zitadel.yourdomain.com/oidc/v1/userinfo
JWKS URL:          https://zitadel.yourdomain.com/oauth/v2/keys
Issuer:            https://zitadel.yourdomain.com

Grafana example

In Grafana's configuration (grafana.ini or environment variables):

[auth.generic_oauth]
enabled = true
name = Zitadel
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scopes = openid profile email
auth_url = https://zitadel.yourdomain.com/oauth/v2/authorize
token_url = https://zitadel.yourdomain.com/oauth/v2/token
api_url = https://zitadel.yourdomain.com/oidc/v1/userinfo
allow_sign_up = true

Portainer example

In Portainer's settings under Authentication > OAuth:

The same pattern applies to Nextcloud, Gitea, Outline, BookStack, and most modern self-hosted applications.

Multi-Tenancy

This is Zitadel's standout feature and the main reason to choose it over alternatives.

Zitadel supports multiple organizations within a single instance. Each organization has its own:

This is useful if you:

In Keycloak, the equivalent concept is "realms." In Authentik, multi-tenancy exists but is less developed. Zitadel's multi-tenancy is a first-class feature, not an afterthought.

The API-First Approach

Everything you can do in Zitadel's UI, you can do through its API. This is what "API-first" means in practice:

# Create a user via API
curl -X POST https://zitadel.yourdomain.com/v2/users/human \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "newuser",
    "profile": {
      "givenName": "New",
      "familyName": "User"
    },
    "email": {
      "email": "[email protected]",
      "isVerified": true
    },
    "password": {
      "password": "SecurePassword123!"
    }
  }'
# List all users in an organization
curl https://zitadel.yourdomain.com/v2/users \
  -H "Authorization: Bearer $TOKEN"

The API uses gRPC under the hood with a REST gateway. Official client libraries exist for Go, .NET, Python, and JavaScript/TypeScript. For automation and CI/CD pipelines, this is significantly more practical than clicking through a web UI.

Service users and machine accounts

Zitadel has a concept of "machine users" for service-to-service authentication. Create a machine user, assign it a key, and use it for API access without human interaction. This is the clean way to integrate Zitadel into automated workflows.

Actions

Zitadel Actions are JavaScript functions that execute during the authentication flow. Think of them as lightweight serverless functions triggered by events:

// Example: Add custom claims based on user metadata
function preUserinfoCreation(ctx, api) {
  if (ctx.v1.user.grants.length > 0) {
    api.v1.claims.setClaim('custom:role', ctx.v1.user.grants[0].roles[0]);
  }
}

Use cases:

Actions aren't as powerful as Authentik's flow system (which lets you build entire custom authentication workflows visually), but they're simpler and lighter-weight.

Resource Usage

One of Zitadel's practical advantages is its resource efficiency:

Component RAM CPU Storage
Zitadel 150-250 MB 0.5 cores idle Minimal
PostgreSQL 100-200 MB 0.5 cores idle ~100 MB base
Total ~300-450 MB ~1 core ~100 MB

Compare this with Keycloak (1 GB+ RAM) or Authentik (~500 MB for the application plus ~300 MB for the worker and Redis). For a homelab server where every 100 MB matters because you're running 20 other services, Zitadel's Go binary is noticeably lighter.

Migration Considerations

Coming from Authelia

If you've outgrown Authelia and want a full identity provider, migrating means:

  1. Setting up Zitadel alongside Authelia
  2. Creating users in Zitadel
  3. Reconfiguring each service from forward-auth (Authelia) to OIDC (Zitadel)
  4. Eventually removing Authelia

There's no automated migration path. Each service needs to be reconfigured individually.

Coming from Keycloak

Zitadel doesn't have a Keycloak import tool, but the OIDC endpoints are standard. Update your services' OIDC configuration to point to Zitadel's endpoints instead of Keycloak's. Users need to be recreated (or imported via API).

Coming from Authentik

Same situation — no automated migration. The API makes bulk user creation scriptable, but you'll need to reconfigure each service.

The honest truth: migrating between IAM systems is always painful. Pick one early and stick with it unless you have a strong reason to switch.

The Honest Trade-offs

Zitadel is great if:

Zitadel is not ideal if:

Documentation quality: Zitadel's official documentation is good for API reference and concepts, but thinner on self-hosting guides and homelab-specific tutorials compared to Authentik. Expect to piece things together more from docs, GitHub issues, and the Discord community.

Community size: Zitadel's community is growing but still smaller than Keycloak or Authentik. You'll find fewer blog posts, YouTube tutorials, and forum answers when you run into issues. If community support matters to you, factor this in.

Bottom line: Zitadel is the right choice when your needs go beyond basic homelab SSO. If you're building something with multi-tenant requirements, heavy API usage, or you're planning ahead for growth, Zitadel's architecture is designed exactly for that. For a straightforward homelab setup, Authentik or Authelia will get you there with less friction.

Resources