Security Azure

ZITADEL on Ubuntu 24.04 on Azure User Guide

| Product: ZITADEL on Ubuntu 24.04 LTS on Azure

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.

The ZITADEL Console home dashboard after signing in with the per VM administrator credentials

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.service and nginx.service as 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 ZITADEL API, Login v2 and bundled PostgreSQL containers running under systemd

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:

The API rejects requests with no token (401) and accepts only the per VM token (200); health stays public

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

The per VM access notes, with the masterkey, admin password, API token and database password masked

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.

The ZITADEL Login v2 screen, which gates the Console until you enter the per VM administrator credentials

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.

The ZITADEL Console home dashboard, signed in as the administrator

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 Users tab listing the per VM administrator account as Active

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.

The Projects tab showing the default project created during setup

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'

The OIDC discovery document showing the per VM issuer and identity endpoints

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.