docs: simplify compose example (#10407)

# Which Problems Are Solved

Using the compose configuration with for existing installation doesn't
work.
It is not appreciated in the whole community, that we use Traefik for
the deployment example.

# How the Problems Are Solved

- The config we used before v4 is reused and extended in a compatible
way.
- Traefik is removed, Zitadel is accessible again at localhost:8080 and
the login at localhost:3000. Deploying with Traefik is described already
here http://localhost:3000/docs/self-hosting/manage/reverseproxy/traefik
- A page is added that describes how to create a login client and switch
from login v1 to v2. The page is linked in a hint in the compose
example.

# Additional Changes

- The config also sets up a machine user and documents it inline. This
allows us to avoid using the cumbersome separate docker-compose-sa.yaml

# Additional Context

- Closes #10379 
- Internal discussion
https://zitadel.slack.com/archives/C08TL9AURL7/p1754471184222879
- Discord about upgrading to v2 login:
-
https://discord.com/channels/927474939156643850/927866013545025566/threads/1401950163940933804
-
https://discord.com/channels/927474939156643850/927866013545025566/1401312126030708756
- Discord about not found errors:
-
https://discord.com/channels/927474939156643850/927866013545025566/threads/1401173877941473291
-
https://discord.com/channels/927474939156643850/927866013545025566/1401045717849604227
-
https://discord.com/channels/927474939156643850/927866013545025566/1401173877941473291
-
https://discord.com/channels/927474939156643850/927866013545025566/1401301168998584361
This commit is contained in:
Elio Bischof
2025-08-06 22:48:54 +02:00
committed by GitHub
parent b6ebabb962
commit 7e184727b0
9 changed files with 149 additions and 183 deletions

View File

@@ -1 +1 @@
login-client-pat *.pat

View File

@@ -1,8 +1,6 @@
Open your favorite internet browser and navigate to [http://localhost:8080/ui/console](http://localhost:8080/ui/console). Open your favorite internet browser and navigate to http://localhost:8080/ui/console?login_hint=zitadel-admin@zitadel.localhost.
This is the default IAM admin users login: Enther the password *Password1!* to log in.
- **username**: *zitadel-admin@<span></span>zitadel.localhost*
- **password**: *Password1!*
:::info :::info
In the above login hint in the URL, replace localhost with your configured external domain, if any. e.g. with *zitadel-admin@<span></span>zitadel.sso.my.domain.tld*
In the above username, replace localhost with your configured external domain, if any. e.g. with *zitadel-admin@<span></span>zitadel.sso.my.domain.tld* :::

View File

@@ -5,61 +5,45 @@ sidebar_label: Docker Compose
import CodeBlock from '@theme/CodeBlock'; import CodeBlock from '@theme/CodeBlock';
import DockerComposeSource from '!!raw-loader!./docker-compose.yaml' import DockerComposeSource from '!!raw-loader!./docker-compose.yaml'
import ExampleZitadelConfigSource from '!!raw-loader!./example-zitadel-config.yaml' import Disclaimer from './_disclaimer.mdx'
import ExampleZitadelSecretsSource from '!!raw-loader!./example-zitadel-secrets.yaml' import DefaultUser from './_defaultuser.mdx'
import ExampleZitadelInitStepsSource from '!!raw-loader!./example-zitadel-init-steps.yaml' import Next from './_next.mdx'
import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx';
The stack consists of four long-running containers and a couple of short-lived containers:
- A [Traefik](https://doc.traefik.io/traefik/) reverse proxy container with upstream HTTP/2 enabled, issuing a self-signed TLS certificate.
- A Login container that is accessible via Traefik at `/ui/v2/login`
- A Zitadel container that is accessible via Traefik at all other paths than `/ui/v2/login`.
- An insecure [PostgreSQL](https://www.postgresql.org/docs/current/index.html).
The Traefik container and the login container call the Zitadel container via the internal Docker network at `h2c://zitadel:8080`
The setup is tested against Docker version 28.3.2 and Docker Compose version v2.38.2 The setup is tested against Docker version 28.3.2 and Docker Compose version v2.38.2
By executing the commands below, you will download the following files: ## Docker compose
By executing the commands below, you will download the following file:
<details> <details>
<summary>docker-compose.yaml</summary> <summary>docker-compose.yaml</summary>
<CodeBlock language="yaml">{DockerComposeSource}</CodeBlock> <CodeBlock language="yaml">{DockerComposeSource}</CodeBlock>
</details> </details>
<details>
<summary>example-zitadel-config.yaml</summary>
<CodeBlock language="yaml">{ExampleZitadelConfigSource}</CodeBlock>
</details>
<details>
<summary>example-zitadel-secrets.yaml</summary>
<CodeBlock language="yaml">{ExampleZitadelSecretsSource}</CodeBlock>
</details>
<details>
<summary>example-zitadel-init-steps.yaml</summary>
<CodeBlock language="yaml">{ExampleZitadelInitStepsSource}</CodeBlock>
</details>
```bash ```bash
# Download the docker compose example configuration. # Download the docker compose example configuration.
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/deploy/docker-compose.yaml wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/deploy/docker-compose.yaml
# Download and adjust the example configuration file containing standard configuration. # Make sure you have the latest image versions
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/deploy/example-zitadel-config.yaml
# Download and adjust the example configuration file containing secret configuration.
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/deploy/example-zitadel-secrets.yaml
# Download and adjust the example configuration file containing database initialization configuration.
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/deploy/example-zitadel-init-steps.yaml
# Make sure you have the latest version of the images
docker compose pull docker compose pull
# Run the containers # Run the PostgreSQL database, the Zitadel API and the Zitadel login.
docker compose up docker compose up
``` ```
Open your favorite internet browser at https://localhost/ui/console?login_hint=zitadel-admin@zitadel.localhost. <DefaultUser components={props.components} />
Your browser warns you about the insecure self-signed TLS certificate. As localhost resolves to your local machine, you can safely proceed.
Use the password *Password1!* to log in.
Read more about [the login process](/guides/integrate/login/oidc/login-users). :::info
If you ran these commands for an existing instance that still uses the login v1, [create a login client for it to the now running v2 login](/self-hosting/manage/login-client#create-login-client).
Move the login client PAT to `./login-client.pat` and restart the login container.
```bash
docker compose restart login
```
Now, [enable the Login UI for all users](/self-hosting/manage/login-client#require-login-v2)
:::
<NoteInstanceNotFound/>
<Next components={props.components} />
<Disclaimer components={props.components} />

View File

@@ -1,117 +1,98 @@
services: services:
zitadel:
db:
image: postgres:17-alpine
restart: unless-stopped restart: unless-stopped
image: ghcr.io/zitadel/zitadel:latest
command: start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
environment: environment:
- POSTGRES_USER=root ZITADEL_EXTERNALSECURE: false
- POSTGRES_PASSWORD=postgres ZITADEL_TLS_ENABLED: false
networks: ZITADEL_DATABASE_POSTGRES_HOST: db
- 'storage' ZITADEL_DATABASE_POSTGRES_PORT: 5432
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
# By configuring a login client, the setup job creates a user of type machine with the role IAM_LOGIN_CLIENT.
# It writes a PAT to the path specified in ZITADEL_FIRSTINSTANCE_LOGINCLIENTPATPATH.
# The PAT is passed to the login container via the environment variable ZITADEL_SERVICE_USER_TOKEN_FILE.
ZITADEL_FIRSTINSTANCE_LOGINCLIENTPATPATH: /current-dir/login-client.pat
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: false
ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_USERNAME: login-client
ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_NAME: Automatically Initialized IAM_LOGIN_CLIENT
ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_PAT_EXPIRATIONDATE: '2029-01-01T00:00:00Z'
ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_REQUIRED: true
ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_BASEURI: http://localhost:3000/ui/v2/login
ZITADEL_OIDC_DEFAULTLOGINURLV2: http://localhost:3000/ui/v2/login/login?authRequest=
ZITADEL_OIDC_DEFAULTLOGOUTURLV2: http://localhost:3000/ui/v2/login/logout?post_logout_redirect=
ZITADEL_SAML_DEFAULTLOGINURLV2: http://localhost:3000/ui/v2/login/login?samlRequest=
# By configuring a machine, the setup job creates a user of type machine with the role IAM_OWNER.
# It writes a personal access token (PAT) to the path specified in ZITADEL_FIRSTINSTANCE_PATPATH.
# The PAT can be used to provision resources with [Terraform](/docs/guides/manage/terraform-provider), for example.
ZITADEL_FIRSTINSTANCE_PATPATH: /current-dir/admin.pat
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: admin
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Automatically Initialized IAM_OWNER
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE: 1
healthcheck: healthcheck:
test: [ "CMD-SHELL", "pg_isready", "-d", "db_prod" ] test:
- CMD
- /app/zitadel
- ready
interval: 10s interval: 10s
timeout: 60s timeout: 60s
retries: 5 retries: 5
start_period: 10s start_period: 10s
volumes: volumes:
- 'data:/var/lib/postgresql/data:rw' - .:/current-dir:delegated
ports:
zitadel-init: - 8080:8080
restart: 'no' - 3000:3000
networks: networks:
- 'storage' - zitadel
image: 'ghcr.io/zitadel/zitadel:v4.0.0-rc.2'
command: [ init, --config, /example-zitadel-config.yaml, --config, /example-zitadel-secrets.yaml ]
depends_on: depends_on:
db: db:
condition: 'service_healthy' condition: service_healthy
volumes:
- './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro'
- './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro'
zitadel-setup:
restart: 'no'
networks:
- 'storage'
image: 'ghcr.io/zitadel/zitadel:v4.0.0-rc.2'
command: [ setup, --config, /current-dir/example-zitadel-config.yaml, --config, /current-dir/example-zitadel-secrets.yaml, --steps, /current-dir/example-zitadel-init-steps.yaml, --masterkey, MasterkeyNeedsToHave32Characters ]
depends_on:
zitadel-init:
condition: 'service_completed_successfully'
restart: false
volumes:
- '.:/current-dir:rw'
zitadel:
restart: 'unless-stopped'
networks:
- 'backend'
- 'storage'
labels:
- "traefik.http.routers.zitadel.rule=!PathPrefix(`/ui/v2/login`)"
- "traefik.http.routers.zitadel.tls=true" # Traefik uses a self-signed certificate
- "traefik.http.services.zitadel.loadbalancer.passhostheader=true"
- "traefik.http.services.zitadel.loadbalancer.server.scheme=h2c"
- "traefik.http.services.zitadel.loadbalancer.server.port=8080"
image: 'ghcr.io/zitadel/zitadel:v4.0.0-rc.2'
command: [ start, --config, /example-zitadel-config.yaml, --config, /example-zitadel-secrets.yaml, --masterkey, MasterkeyNeedsToHave32Characters ]
depends_on:
zitadel-setup:
condition: 'service_completed_successfully'
restart: true
volumes:
- './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro'
- './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro'
healthcheck:
test: [ "CMD", "/app/zitadel", "ready", "--config", "/example-zitadel-config.yaml", "--config", "/example-zitadel-secrets.yaml" ]
interval: 10s
timeout: 60s
retries: 5
start_period: 10s
login: login:
restart: 'unless-stopped' restart: unless-stopped
labels: image: ghcr.io/zitadel/zitadel-login:latest
- "traefik.http.routers.login.rule=PathPrefix(`/ui/v2/login`)"
- "traefik.http.routers.login.tls=true" # Traefik uses a self-signed certificate
- "traefik.http.services.login.loadbalancer.passhostheader=true"
- "traefik.http.services.login.loadbalancer.server.port=3000"
image: 'ghcr.io/zitadel/zitadel-login:v4.0.0-rc.2'
# If you can't use the network_mode service:zitadel, you can pass the environment variable CUSTOM_REQUEST_HEADERS=Host:localhost instead. # If you can't use the network_mode service:zitadel, you can pass the environment variable CUSTOM_REQUEST_HEADERS=Host:localhost instead.
network_mode: service:zitadel
environment: environment:
- ZITADEL_API_URL=http://localhost:8080 - ZITADEL_API_URL=http://localhost:8080
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login - NEXT_PUBLIC_BASE_PATH=/ui/v2/login
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client-pat - ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
user: "${UID:-1000}" user: "${UID:-1000}"
network_mode: service:zitadel
volumes: volumes:
- '.:/current-dir:ro' - .:/current-dir:ro
depends_on:
zitadel-setup:
condition: 'service_completed_successfully'
restart: false
traefik:
image: traefik:latest
command: --providers.docker --api.insecure=true --entrypoints.websecure.address=:443 --log.level=DEBUG --accesslog
networks:
- 'backend'
ports:
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
depends_on: depends_on:
zitadel: zitadel:
condition: 'service_healthy' condition: service_healthy
login: restart: false
condition: 'service_started'
db:
restart: unless-stopped
image: postgres:17-alpine
environment:
PGUSER: postgres
POSTGRES_PASSWORD: postgres
healthcheck:
test:
- CMD-SHELL
- pg_isready
- -d
- zitadel
- -U
- postgres
interval: 10s
timeout: 30s
retries: 5
start_period: 20s
networks:
- zitadel
networks: networks:
storage: zitadel:
backend:
volumes:
data:

View File

@@ -1,22 +0,0 @@
# All possible options and their defaults: https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml
ExternalSecure: true
ExternalPort: 443
# Traefik terminates TLS. Inside the Docker network, we use plain text.
TLS.Enabled: false
# If not using the docker compose example, adjust these values for connecting ZITADEL to your PostgreSQL
Database:
postgres:
Host: 'db'
Port: 5432
Database: zitadel
User.SSL.Mode: 'disable'
Admin.SSL.Mode: 'disable'
# Access logs allow us to debug Network issues
LogStore.Access.Stdout.Enabled: true
# Skipping the MFA init step allows us to immediately authenticate at the console
DefaultInstance.LoginPolicy.MfaInitSkipLifetime: "0s"

View File

@@ -1,11 +0,0 @@
# All possible options and their defaults: https://github.com/zitadel/zitadel/blob/main/cmd/setup/steps.yaml
FirstInstance:
LoginClientPatPath: '/current-dir/login-client-pat'
Org:
# We want to authenticate immediately at the console without changing the password
Human.PasswordChangeRequired: false
LoginClient:
Machine:
Username: 'login-client'
Name: 'Automatically Initialized IAM Login Client'
Pat.ExpirationDate: '2029-01-01T00:00:00Z'

View File

@@ -1,12 +0,0 @@
# All possible options and their defaults: https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml
# If not using the docker compose example, adjust these values for connecting ZITADEL to your PostgreSQL
Database:
postgres:
User:
# If the user doesn't exist already, it is created
Username: 'zitadel_user'
Password: 'zitadel'
Admin:
Username: 'root'
Password: 'postgres'

View File

@@ -0,0 +1,47 @@
---
title: Connect your Self-Hosted Login UI to Zitadel
sidebar_label: Create a Login Client
---
To enable your self-hosted Login UI to connect to the Zitadel API, it needs a token for a user with the IAM_LOGIN_CLIENT role.
On new installations, the Zitadel setup job can be configured to automatically write a Personal Access Token (PAT) for the login client.
Check out [one of the deployment examples](https://zitadel.com/docs/self-hosting/deploy/overview) to learn how to do this.
However, if you want to replace the v1 login of an existing installation by a self-hosted v2 login, the setup job won't execute these steps.
In that case, you can create a new PAT for the login client manually.
## Create a Login Client User{#create-login-client}
In the following URLs, replace the base URL and the user ID according to your environment.
1. Create a new machine user, for example at http://localhost:8080/ui/console/users/create-machine
2. Create a PAT, for example at http://localhost:8080/ui/console/users/332169800719532035?new=true&id=pat
3. Save the PAT to a file, for example `/path/on/your/host/login-client.pat`
4. Make sure the user has the `Iam Login Client` role (internally called `IAM_LOGIN_CLIENT`), for example at http://localhost:8080/ui/console/instance/members
# Configure the Login UI
Make sure your Login UI has the environment variable `ZITADEL_SERVICE_USER_TOKEN` set with your PAT.
If you run the Login UI with Docker, you can also mount the file into the container and reference it by passing the environment variable `ZITADEL_SERVICE_USER_TOKEN_FILE`.
For example:
```bash
docker run -p 3000:3000 -v /path/on/your/host/login-client.pat:/path/in/container/login-client.pat:ro -e ZITADEL_SERVICE_USER_TOKEN_FILE=/path/in/container/login-client.pat ghcr.io/zitadel/zitadel-login:latest
```
# Enable the Login UI for all users{#require-login-v2}
:::caution
Before doing this, make sure you have a working PAT for an Iam Owner user.
In case something goes wrong and you lock yourself out from the login screen, you can revert the changes.
Create a machine user PAT like you created the [login client PAT above](#create-login-client), but give the user the Iam Owner role (internally called `IAM_OWNER`).
:::
Enable the `Login V2` feature flag, for example at the bottom of http://localhost:8080/ui/console/instance?id=features.
Enter the base URI of your Login UI, for example `http://localhost:3000/ui/v2/login`.
# Test
That's it!
Click your users avatar in the top right corner of the console and select `Log in With Another Account`.
You should see the new Login UI.

View File

@@ -1094,6 +1094,7 @@ module.exports = {
items: [ items: [
"self-hosting/manage/production", "self-hosting/manage/production",
"self-hosting/manage/productionchecklist", "self-hosting/manage/productionchecklist",
"self-hosting/manage/login-client",
"self-hosting/manage/configure/configure", "self-hosting/manage/configure/configure",
{ {
type: "category", type: "category",