diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/.gitignore b/docs/docs/self-hosting/deploy/loadbalancing-example/.gitignore new file mode 100644 index 0000000000..bd98bacd66 --- /dev/null +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/.gitignore @@ -0,0 +1 @@ +.env-file diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml b/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml index d1d8c95bb2..013fc2aa22 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml @@ -1,48 +1,157 @@ services: - traefik: + db: + image: postgres:17-alpine + restart: unless-stopped + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=postgres networks: - - 'zitadel' - image: "traefik:latest" - ports: - - "80:80" - - "443:443" + - 'storage' + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"] + interval: 10s + timeout: 60s + retries: 5 + start_period: 10s volumes: - - "./example-traefik.yaml:/etc/traefik/traefik.yaml" + - 'data:/var/lib/postgresql/data:rw' - zitadel: - restart: 'always' + zitadel-init: + restart: 'no' networks: - - 'zitadel' + - 'storage' image: 'ghcr.io/zitadel/zitadel:latest' - command: 'start-from-init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey "${ZITADEL_MASTERKEY}" --tlsMode external' + command: 'init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml' depends_on: db: 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' + # We use the debug image so we have the environment to + # - create the .env file for the login to authenticate at Zitadel + # - set the correct permissions for the .env-file folder + image: 'ghcr.io/zitadel/zitadel:latest-debug' + user: root + entrypoint: '/bin/sh' + command: + - -c + - > + /app/zitadel setup + --config /example-zitadel-config.yaml + --config /example-zitadel-secrets.yaml + --steps /example-zitadel-init-steps.yaml + --masterkey ${ZITADEL_MASTERKEY} && + mv /pat /.env-file/pat || exit 0 && + echo ZITADEL_SERVICE_USER_TOKEN=$(cat /.env-file/pat) > /.env-file/.env && + chown -R 1001:${GID} /.env-file && + chmod -R 770 /.env-file + environment: + - GID + depends_on: + zitadel-init: + condition: 'service_completed_successfully' + restart: false + volumes: + - './.env-file:/.env-file:rw' + - './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro' + - './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro' - './example-zitadel-init-steps.yaml:/example-zitadel-init-steps.yaml:ro' - db: - image: postgres:17-alpine - restart: always - environment: - - POSTGRES_USER=root - - POSTGRES_PASSWORD=postgres + zitadel: + restart: 'unless-stopped' networks: - - 'zitadel' + - 'backend' + - 'storage' + image: 'ghcr.io/zitadel/zitadel:latest' + command: > + start --config /example-zitadel-config.yaml + --config /example-zitadel-secrets.yaml + --masterkey ${ZITADEL_MASTERKEY} + 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' + ports: + - "8080:8080" healthcheck: - test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"] + test: [ + "CMD", "/app/zitadel", "ready", + "--config", "/example-zitadel-config.yaml", + "--config", "/example-zitadel-secrets.yaml" + ] interval: 10s timeout: 60s retries: 5 - start_period: 10s + start_period: 10s + + # The use-new-login service configures Zitadel to use the new login v2 for all applications. + # It also gives the setupped machine user the necessary IAM_LOGIN_CLIENT role. + use-new-login: + restart: 'on-failure' + user: "1001" + networks: + - 'backend' + image: 'badouralix/curl-jq:alpine' + entrypoint: '/bin/sh' + command: + - -c + - > + curl -X PUT -H "Host: 127.0.0.1.sslip.io" -H "Authorization: Bearer $(cat ./.env-file/pat)" --insecure http://zitadel:8080/v2/features/instance -d '{"loginV2": {"required": true}}' && + LOGIN_USER=$(curl --fail-with-body -H "Host: 127.0.0.1.sslip.io" -H "Authorization: Bearer $(cat ./.env-file/pat)" --insecure http://zitadel:8080/auth/v1/users/me | jq -r '.user.id') && + curl -X PUT -H "Host: 127.0.0.1.sslip.io" -H "Authorization: Bearer $(cat ./.env-file/pat)" --insecure http://zitadel:8080/admin/v1/members/$${LOGIN_USER} -d '{"roles": ["IAM_OWNER", "IAM_LOGIN_CLIENT"]}' volumes: - - 'data:/var/lib/postgresql/data:rw' + - './.env-file:/.env-file:ro' + depends_on: + zitadel: + condition: 'service_healthy' + restart: false + + login: + restart: 'unless-stopped' + networks: + - 'backend' + image: 'ghcr.io/zitadel/login:main' + environment: + - ZITADEL_API_URL=http://zitadel:8080 + - CUSTOM_REQUEST_HEADERS=Host:127.0.0.1.sslip.io + - NEXT_PUBLIC_BASE_PATH="/ui/v2/login" + user: "${UID:-1000}" + volumes: + - './.env-file:/.env-file:ro' + depends_on: + zitadel: + condition: 'service_healthy' + restart: false + + traefik: + restart: 'unless-stopped' + networks: + - 'backend' + image: "traefik:latest" + ports: + - "80:80" + - "443:443" + volumes: + - "./example-traefik.yaml:/etc/traefik/traefik.yaml" + depends_on: + zitadel: + condition: 'service_healthy' + login: + condition: 'service_started' networks: - zitadel: + storage: + backend: volumes: data: diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/example-traefik.yaml b/docs/docs/self-hosting/deploy/loadbalancing-example/example-traefik.yaml index c16f74a46d..a3af425172 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/example-traefik.yaml +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/example-traefik.yaml @@ -4,66 +4,37 @@ log: accessLog: {} entrypoints: - web: - address: ":80" - websecure: address: ":443" -tls: - stores: - default: - # generates self-signed certificates - defaultCertificate: - providers: file: filename: /etc/traefik/traefik.yaml http: - middlewares: - zitadel: - headers: - isDevelopment: false - allowedHosts: - - 'my.domain' - customRequestHeaders: - authority: 'my.domain' - redirect-to-https: - redirectScheme: - scheme: https - port: 443 - permanent: true - routers: - # Redirect HTTP to HTTPS - router0: + login: entryPoints: - - web - middlewares: - - redirect-to-https - rule: 'HostRegexp(`my.domain`, `{subdomain:[a-z]+}.my.domain`)' - service: zitadel - # The actual ZITADEL router - router1: + - websecure + service: login + rule: 'Host(`127.0.0.1.sslip.io`) && PathPrefix(`/ui/v2/login`)' + tls: {} + zitadel: entryPoints: - websecure service: zitadel - middlewares: - - zitadel - rule: 'HostRegexp(`my.domain`, `{subdomain:[a-z]+}.my.domain`)' - tls: - domains: - - main: "my.domain" - sans: - - "*.my.domain" - - "my.domain" + rule: 'Host(`127.0.0.1.sslip.io`) && !PathPrefix(`/ui/v2/login`)' + tls: {} - # Add the service services: + login: + loadBalancer: + servers: + - url: http://login:3000 + passHostHeader: true zitadel: loadBalancer: servers: - # h2c is the scheme for unencrypted HTTP/2 - url: h2c://zitadel:8080 passHostHeader: true + diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-config.yaml b/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-config.yaml index 392bf1148e..fadd39373d 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-config.yaml +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-config.yaml @@ -1,26 +1,29 @@ # All possible options and their defaults: https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml -Log: - Level: 'info' -# Make ZITADEL accessible over HTTPs, not HTTP ExternalSecure: true -ExternalDomain: my.domain +ExternalDomain: 127.0.0.1.sslip.io 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' + User.SSL.Mode: 'disable' + Admin.SSL.Mode: 'disable' -LogStore: - Access: - Stdout: - Enabled: true +# By default, ZITADEL should redirect to /ui/v2/login +OIDC: + DefaultLoginURLV2: "/ui/v2/login/login?authRequest=" # ZITADEL_OIDC_DEFAULTLOGINURLV2 + DefaultLogoutURLV2: "/ui/v2/login/logout?post_logout_redirect=" # ZITADEL_OIDC_DEFAULTLOGOUTURLV2 +SAML.DefaultLoginURLV2: "/ui/v2/login/login?authRequest=" # ZITADEL_SAML_DEFAULTLOGINURLV2 + +# 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" diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-init-steps.yaml b/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-init-steps.yaml index 804e3d18d8..be63164ced 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-init-steps.yaml +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/example-zitadel-init-steps.yaml @@ -1,8 +1,12 @@ # All possible options and their defaults: https://github.com/zitadel/zitadel/blob/main/cmd/setup/steps.yaml FirstInstance: + PatPath: '/pat' Org: - Name: 'My Org' + # We want to authenticate immediately at the console without changing the password Human: - # use the loginname root@my-org.my.domain - Username: 'root' - Password: 'RootPassword1!' + PasswordChangeRequired: false + Machine: + Machine: + Username: 'login-container' + Name: 'Login Container' + Pat.ExpirationDate: '2029-01-01T00:00:00Z' diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx b/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx index d5e3984568..88cd4c7700 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx @@ -1,5 +1,5 @@ --- -title: A ZITADEL Load Balancing Example +title: A Zitadel Load Balancing Example --- import CodeBlock from '@theme/CodeBlock'; @@ -8,16 +8,16 @@ import ExampleTraefikSource from '!!raw-loader!./example-traefik.yaml' import ExampleZITADELConfigSource from '!!raw-loader!./example-zitadel-config.yaml' import ExampleZITADELSecretsSource from '!!raw-loader!./example-zitadel-secrets.yaml' import ExampleZITADELInitStepsSource from '!!raw-loader!./example-zitadel-init-steps.yaml' -import NoteInstanceNotFound from '../troubleshooting/_note_instance_not_found.mdx'; -With this example configuration, you create a near production environment for ZITADEL with [Docker Compose](https://docs.docker.com/compose/). - -The stack consists of three long-running containers: -- A [Traefik](https://doc.traefik.io/traefik/) reverse proxy with upstream HTTP/2 enabled, issuing a self-signed TLS certificate. -- A secure ZITADEL container configured for a custom domain. As we terminate TLS with Traefik, we configure ZITADEL for `--tlsMode external`. +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 setup is tested against Docker version 20.10.17 and Docker Compose version v2.2.3 +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.0.4 and Docker Compose version v2.34.0 By executing the commands below, you will download the following files: @@ -64,22 +64,11 @@ tr -dc A-Za-z0-9 ./zitadel-masterkey export ZITADEL_MASTERKEY="$(cat ./zitadel-masterkey)" # Run the database and application containers -docker compose up --detach +docker compose up --detach --wait ``` -Make `127.0.0.1` available at `my.domain`. For example, this can be achieved with an entry `127.0.0.1 my.domain` in the `/etc/hosts` file. - -Open your favorite internet browser at [https://my.domain/ui/console/](https://my.domain/ui/console/). -You can safely proceed, if your browser warns you about the insecure self-signed TLS certificate. -This is the IAM admin users login according to your configuration in the [example-zitadel-init-steps.yaml](./example-zitadel-init-steps.yaml): -- **username**: *root@my-org.my.domain* -- **password**: *RootPassword1!* +Open your favorite internet browser at https://127.0.0.1.sslip.io/ui/console?login_hint=zitadel-admin@zitadel.127.0.0.1.sslip.io. +Your browser warns you about the insecure self-signed TLS certificate. As 127.0.0.1.sslip.io resolves to your localhost, you can safely proceed. +Use the password *Password1!* to log in. Read more about [the login process](/guides/integrate/login/oidc/login-users). - - - -## Troubleshooting - -You can connect to the database like this: `docker exec -it loadbalancing-example-db-1 psql --host localhost` -For example, to show all login names: `docker exec -it loadbalancing-example-db-1 psql -d zitadel --host localhost -c 'select * from projections.login_names3'`