ZITADEL on Ubuntu 24.04 on Azure User Guide
Overview
ZITADEL is a cloud native identity and access management platform: single sign on over OpenID Connect, OAuth2 and SAML, passwordless and passkey login, multi factor authentication, and multi tenancy with organizations, projects, roles and self service, all managed from a full web Console. The cloudimg image ships the free and open source, AGPL-3.0 self hosted ZITADEL, run the officially supported way as the upstream containers alongside a bundled PostgreSQL, orchestrated by Docker Compose under systemd and fronted by nginx with TLS. All images are pinned by digest and captured into the VM, so your instance starts in seconds. Because ZITADEL is itself the identity provider, nothing ships with a known secret: a unique 32 character masterkey, a unique initial admin password, a unique database password and unique API tokens are generated for each VM on first boot, before the port is reachable. Backed by 24/7 cloudimg support.
ZITADEL is a trademark of its respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by ZITADEL. It ships the free and open source AGPL-3.0 self hosted software, unmodified.

What is included:
- ZITADEL v4.16.1 (the AGPL-3.0 self hosted platform), API and Console pinned by image digest
- The required ZITADEL Login v2 UI container, pinned by image digest, served at
/ui/v2/login - A bundled PostgreSQL 17 database, pinned by image digest, reachable only inside a private Docker network (never published to a host port)
- Docker Engine with the API and login published to the loopback interface only, fronted by nginx with TLS on port
443 zitadel.service,zitadel-firstboot.service,zitadel-postboot.serviceandnginx.serviceas systemd units, enabled and active on boot- A unique 32 character masterkey, a unique admin password, a unique PostgreSQL password and unique API tokens generated per VM on first boot, never baked into the image
- A clean, empty database on first boot: no default login, no shipped secret, no prior data
- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; increase the size for heavier workloads. NSG inbound: allow 22/tcp from your management network and 443/tcp for the Console and the identity endpoints (80/tcp is served too and redirects to 443). ZITADEL binds the OIDC issuer to the address you reach it on, so the image resolves your VM public IP on first boot; for production, move to your own domain with a real certificate (see the final section).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for ZITADEL by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTPS (443). Then Review + create and Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name zitadel \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name zitadel --port 443 --priority 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
On first boot the image generates a unique masterkey, admin password and database password, initialises a clean PostgreSQL database, runs ZITADEL's setup, and starts the platform. Confirm the systemd services are active and that all three containers (the API, the Login v2 UI and the bundled PostgreSQL) are up:
systemctl is-active docker zitadel nginx
sudo docker ps --format 'table {{.Names}}\t{{.Status}}'
All three services report active, and zitadel-zitadel-api-1, zitadel-zitadel-login-1 and zitadel-postgres-1 are running.

The API and login are published to the loopback interface only and fronted by nginx; the bundled PostgreSQL is never exposed on a host port. The public liveness endpoint returns 200 with no credentials:
curl -sk -o /dev/null -w 'health: HTTP %{http_code}\n' https://127.0.0.1/debug/healthz
Step 5 - Secure by default: no login ships with the image
ZITADEL is the identity provider for your other applications, so it must never ship with a known credential. Every API request without a valid token is rejected, and the initial admin exists only with the password generated for this VM. Confirm the API is locked down; this request sends no token and uses the address ZITADEL was configured with on first boot:
D=$(sudo grep '^ZITADEL_EXTERNALDOMAIN=' /etc/zitadel/zitadel.env | cut -d= -f2-)
curl -sk --resolve "$D:443:127.0.0.1" -o /dev/null -w 'no token: HTTP %{http_code}\n' "https://$D/auth/v1/users/me"
It returns HTTP 401. A valid token is accepted, while the public health endpoint stays open:

Your unique secrets are written to /root/zitadel-credentials.txt, readable only by root:
sudo stat -c '%a %U:%G' /root/zitadel-credentials.txt
It reports 600 root:root. View the notes (the masterkey, the admin login name and password, an administrator API token, the database password and the URLs) with:
sudo cat /root/zitadel-credentials.txt

Step 6 - Sign in to the Console
Browse to https://<vm-public-ip>/ui/console. The VM uses a per VM self signed certificate, so accept the browser warning (replace it with your own certificate for production, see the final section). ZITADEL redirects you to its Login v2 screen, which asks for your login name first, then your password.

Sign in with the administrator login name and password from /root/zitadel-credentials.txt:
Login name: <ADMIN_LOGINNAME> (for example zitadel-admin@zitadel.<vm-public-ip>)
Password: <ADMIN_PASSWORD>
The Console opens on the home dashboard, with the top navigation for Organization, Projects, Users, Role Assignments, Actions and Settings.

Step 7 - Manage users and projects
Open the Users tab to see the accounts in your organization. On a fresh VM this is the single administrator created on first boot; from here you add human users and service accounts, and manage their login policies and multi factor settings.

The Projects tab hosts the applications your users authenticate against. Each project holds one or more applications (a web frontend, a mobile app, a backend service), each with its own OIDC or SAML configuration.

Step 8 - The OIDC issuer your applications integrate with
Applications integrate with ZITADEL through its OpenID Connect issuer. The discovery document advertises the authorization, token, userinfo and JWKS endpoints, all bound to the address this VM was configured with on first boot:
D=$(sudo grep '^ZITADEL_EXTERNALDOMAIN=' /etc/zitadel/zitadel.env | cut -d= -f2-)
curl -sk --resolve "$D:443:127.0.0.1" "https://$D/.well-known/openid-configuration" | python3 -m json.tool | grep -E '"issuer"|_endpoint'

You can also drive ZITADEL's management and auth APIs with the administrator API token from your notes. From your own machine, substitute your VM public IP and the token:
curl https://<vm-public-ip>/auth/v1/users/me \
-H 'Authorization: Bearer <ADMIN_PAT>'
Step 9 - Production: your own domain with TLS
For production, move ZITADEL to your own domain with a real certificate. Point a DNS record at the VM public IP, set ZITADEL_EXTERNALDOMAIN in /etc/zitadel/zitadel.env to that domain, replace the certificate under /etc/zitadel/certs, then re-apply the stack:
sudo docker compose --env-file /etc/zitadel/zitadel.env -f /etc/zitadel/compose.yaml up -d
Restrict inbound access in your NSG to 443/tcp (and 22/tcp from your management network), and keep /root/zitadel-credentials.txt confidential: the masterkey encrypts ZITADEL's data at rest and the admin credentials grant full control of your identity platform.
Support
This image is supported 24/7 by cloudimg. For help with deployment or configuration, contact support through cloudimg.co.uk.