Zitadel: Cloud-Native Identity and Access Management You Can Self-Host
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:
- User management — Create users, manage profiles, handle password resets
- OIDC/OAuth2 provider — Applications authenticate against Zitadel using standard protocols
- SAML provider — Legacy enterprise SSO support
- Multi-tenancy — Multiple organizations in a single instance, each with their own users and policies
- API-first — Every operation available through gRPC and REST APIs
- Branding — Custom login pages per organization
- Actions — Serverless functions that trigger on auth events (like webhooks but built in)
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
- You want a modern, API-first IAM that feels like it belongs in 2026
- You need multi-tenancy (managing identities for multiple projects or organizations)
- You want low resource usage without sacrificing features
- You prefer Go-based infrastructure over Java (Keycloak) or Python (Authentik)
- You plan to interact with the IAM programmatically (CI/CD pipelines, automation, custom apps)
- You're building something that might grow into a SaaS and need tenant isolation
When to pick alternatives
- Authentik — If you want the most homelab-friendly full IAM with a visual flow designer, excellent documentation, and a large community. Authentik is the most popular choice in the self-hosting community for a reason.
- Keycloak — If you need battle-tested enterprise features, LDAP/Active Directory integration, and don't mind the resource overhead. Keycloak has been around since 2014 and is the most feature-complete option.
- Authelia — If you just want simple SSO in front of your reverse proxy and don't need user management, social login, or OIDC provider capabilities. Authelia does less, but what it does, it does with minimal overhead.
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:
- Click New Application
- Choose Web type
- Select PKCE or Code authentication method
- Set redirect URIs (where the service sends users after login)
For example, configuring Grafana:
- Name: Grafana
- Redirect URIs:
https://grafana.yourdomain.com/login/generic_oauth - Post-logout URIs:
https://grafana.yourdomain.com/login
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:
- Client ID: from Zitadel
- Client Secret: from Zitadel
- Authorization URL:
https://zitadel.yourdomain.com/oauth/v2/authorize - Access Token URL:
https://zitadel.yourdomain.com/oauth/v2/token - Resource URL:
https://zitadel.yourdomain.com/oidc/v1/userinfo - Scopes:
openid profile email
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:
- Users and groups
- Login branding (logo, colors, custom domain)
- Password and MFA policies
- Identity providers (social login configurations)
- Projects and applications
This is useful if you:
- Run services for multiple people or families who shouldn't see each other's data
- Build a SaaS product where each customer needs their own identity space
- Manage separate environments (dev, staging, prod) with isolated user pools
- Run a small business alongside your homelab and want separate identity boundaries
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:
- Add custom claims to tokens based on user attributes
- Call external APIs during login (check if user is in an external system)
- Transform user data before it reaches your application
- Log authentication events to an external service
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:
- Setting up Zitadel alongside Authelia
- Creating users in Zitadel
- Reconfiguring each service from forward-auth (Authelia) to OIDC (Zitadel)
- 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:
- You want a modern, lightweight IAM that doesn't require a JVM
- You need multi-tenancy as a first-class feature
- You plan to interact with IAM programmatically via API
- You value resource efficiency
- You're building something that may scale beyond a personal homelab
- You prefer Go-based infrastructure
Zitadel is not ideal if:
- You want the largest community and most homelab-specific documentation (Authentik wins)
- You want a visual flow designer for custom authentication workflows (Authentik)
- You need deep LDAP/Active Directory integration (Keycloak)
- You just want simple SSO without running a full IAM stack (Authelia)
- You're not comfortable being an early-ish adopter — Zitadel's community is smaller than Keycloak's or Authentik's
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.