Lab - Self Hosting ZITADEL
To self host an OIDC provider, I was investigating Authentik and ZITADEL. Here I did some experiments with ZITADEL.
Preparation
- Host websites using Docker and NPM
Installation
Visit https://zitadel.com/, and go to Quick Start, then go to Self-Hosting, as always. I'm trying Docker whenever I can, so
mkdir zitadel
cd zitadel
First I download the docker-compose.yaml
in the folder:
services:
zitadel:
restart: 'always'
networks:
- 'zitadel'
image: 'ghcr.io/zitadel/zitadel:latest'
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --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'
- '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'
- 'ZITADEL_EXTERNALSECURE=false'
depends_on:
db:
condition: 'service_healthy'
ports:
- '8080:8080'
db:
restart: 'always'
image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=zitadel
networks:
- 'zitadel'
healthcheck:
test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres"]
interval: '10s'
timeout: '30s'
retries: 5
start_period: '20s'
networks:
zitadel:
Then launch it to see what happened.
$ docker compose up -d && docker compose logs -f
It starts fine but I see some "fatal" messages:
...
zitadel-1 | time="..." level=info msg="server is listening on [::]:8080" caller="..."
db-1 | ... [865] FATAL: role "root" does not exist
db-1 | ... [900] FATAL: role "root" does not exist
Searching the web I find this, so I add the line PGUSER=root
in the environment
section for the db
container:
db:
...
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=zitadel
- PGUSER=postgres
Since I was running it not locally but on another machine in the LAN with IP, say, 192.168.0.10
. I cannot just browse to the site via localhost:8080
, but http://192.168.0.10:8080
gives me:
ID=QUERY-1kIjX Message=Instance not found. Make sure you got the domain right. Check out https://zitadel.com/docs/apis/introduction#domains Parent=(unable to set instance using origin http://192.168.0.10:8080 (ExternalDomain is localhost): unable to get instance by host 192.168.0.10:8080: ID=QUERY-1kIjX Message=Errors.IAM.NotFound)
To make it work, I look through ZITADEL site and tried to set the environment variable for zitadel
in docker-compose.yaml
:
zitadel:
...
environment:
...
- 'ZITADEL_EXTERNALDOMAIN=testzidatel.localhost'
Now ZITADEL seems to set up correctly:
... | _____ ___ _____ _ ____ _____ _
... | |__ / |_ _| |_ _| / \ | _ \ | ____| | |
... | / / | | | | / _ \ | | | | | _| | |
... | / /_ | | | | / ___ \ | |_| | | |___ | |___
... | /____| |___| |_| /_/ \_\ |____/ |_____| |_____|
... |
... | ===============================================================
... |
... | Version : v2.49.0
... | TLS enabled : false
... | External Secure : false
... | Console URL : http://testzitadel:8080/ui/console
... | Health Check URL : http://testzitadel:8080/debug/healthz
... |
... | Warning: you're using plain http without TLS. Be aware this is
... | not a secure setup and should only be used for test systems.
... | Visit: https://zitadel.com/docs/self-hosting/manage/tls_modes
... |
... | ===============================================================
I can access the site http://testzitadel:8080/
now, and place the login name and password:
Login Name: zitadel-admin@zitadel.testzitadel
Password: Password1!
And reset my password afterwards. Once inside, I can change my account name and profile.
Now that I have set ZITADEL up locally. I'd like to go further and deploy it under a given domain name.
Production Deployment
There are quite some documents and guidelines for production deployment. Anyways, let's start with Production Setup, which suggests using configuration files instead of relying on environment variables. I guess I will do it later, but I'd like to launch ZITADEL in production mode without reworking the configuration a lot.
docker-compose.yaml
I adapt the given docker-compose.yaml
with some basic changes, especially the networking, to work with NPM:
networks:
mynet:
name: mynet
external: true
services:
zitadel:
restart: 'unless-stopped'
networks:
- 'mynet'
image: 'ghcr.io/zitadel/zitadel:latest'
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'
- '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'
- 'ZITADEL_EXTERNALSECURE=true'
- 'ZITADEL_TLS_ENABLED=false'
- 'ZITADEL_EXTERNALDOMAIN=myauth.mydomain'
- 'ZITADEL_EXTERNALPORT=443'
depends_on:
db:
condition: 'service_healthy'
expose:
- 8080
db:
restart: 'unless-stopped'
image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=zitadel
networks:
- 'mynet'
healthcheck:
test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres"]
interval: '10s'
timeout: '30s'
retries: 5
start_period: '20s'
volumes:
- 'data:/var/lib/postgresql/data:rw'
volumes:
data:
- Network is changed from
zitadel
to externalmynet
to join the NPM network - Expose port
8080
instead of exporting the port - Use named volume for PostgreSQL
In addition, the docker-compose.yaml
above contains additional environment variables. They are based on Configuration Options.
In "Runtime configuration file" section, the file defaults.yaml
contains:
ExternalDomain: localhost # ZITADEL_EXTERNALDOMAIN
ExternalSecure: true # ZITADEL_EXTERNALSECURE
TLS:
Enabled: true # ZITADEL_TLS_ENABLED
SMTPConfiguration:
SMTP:
Host
Host: # ZITADEL_..._SMTP_HOST
User: # ZITADEL_..._SMTP_USER
Password: # ZITADEL_..._SMTP_PASSWORD
TLS: # ZITADEL_..._SMTPCONFIGURATION_TLS
From: # ZITADEL_..._FROM
FromName: # ZITADEL_..._FROMNAME
ReplyToAddress: # ZITADEL_..._REPLYTOADDRESS
So I add the corresponding environment values necessary according to the information above, since we are using NPM, assuming HTTPS on port 443 with SSL certificates covered, and forward to http://zitadel:8080
And in the "Database initialization file" section, the file steps.yaml
contains some useful default settings when running ZITADEL the first time. I didn't include my values here, but if during the first login there there are some problems you may set the corresponding environment variables here.
FirstInstance:
Org:
Human:
UserName: zitadel-admin # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME
Email:
# uses the username if empty
Address: # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_ADDRESS
Verified: true # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_VERIFIED
Password: Password1! # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD
PasswordChangeRequired: true # ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED
...
The problem I had was to enter the wrong admin user name. Assuming my domain ismyauth.mydomain
, I was usingzitadel-admin@myauth.mydomain
but actually it should bezitadel-admin@zitadel.myauth.mydomain
. Then it says the user cannot be found, and when I try to register one anyway, it tried to email the code for me to receive. Not only the email does not work, but also I haven't set up the correct SMTP yet.
As for the master key, as hinted in the "Passing the configuration" section, inside "Docker Compose" tab, I use the command tr -dc A-Za-z0-9 </dev/urandom | head -c 32
to generate the master key for the environment variable ZITADEL_MASTERKEY
, and also change the argument for the command start-from-init
to --masterkeyFromEnv
.
Now launch the container:
docker compose up -d && docker compose logs -f
If no strange logs occur, add the entry in NPM with SSL certificate created.
Enter the username and password to play with it:
Login Name: zitadel-admin@zitadel.myauth.mydomain
Password: RootPassword1!
It may work properly, but you also want to open the browser's dev tools to see if anything unusual.