Appearance
Bootstrap Guide
One-time setup to configure Zitadel for Exto Console. Run this once per environment (local, staging, production). The script is idempotent — safe to re-run.
Prerequisites
- Zitadel running and accessible (e.g.
http://localhost:8080) - Go 1.25+ installed (to run the bootstrap script)
1. Create a bootstrap admin user in Zitadel
- Open Zitadel Console (
http://localhost:8080/ui/console/) - Users > New > Machine User
- Username:
console-admin - Access Token Type: JWT
- Username:
- Memberships > Add IAM membership > IAM_OWNER role
- Personal Access Tokens > New — copy the token
2. Create the bootstrap input file
Create .bootstrap-input.env in the project root:
env
ZITADEL_URL=http://localhost:8080
ZITADEL_PAT=<paste-the-pat-here>
# Optional — defaults shown:
# CONSOLE_URL=http://localhost:5174
# DEVELOPMENT_MODE=trueFor production: set ZITADEL_URL to your prod Zitadel, CONSOLE_URL to the real Console URL, and DEVELOPMENT_MODE=false.
3. Run bootstrap
bash
make bootstrap
# or: go run ./scripts/bootstrap/Output (first run):
Connecting to Zitadel at http://localhost:8080 (developmentMode=true) ...
✓ Admin org: ZITADEL (362930185718267907)
✓ Created project 'Exto Instances': 363006148858609667
✓ Created machine user 'console-worker': 363006148900814851
✓ Granted console-worker IAM Owner
✓ Generated client credentials for console-worker
✓ Created Console Portal OIDC app: appId=... clientId=...
✓ Set Zitadel label policy to light theme
✓ Got signing key from Zitadel
✓ Created Zitadel webhook target → http://localhost:5174/internal/zitadel-events
Bootstrap complete. Values written to .bootstrap.envOutput (re-run — existing resources are reused):
✓ Admin org: ZITADEL (362930185718267907)
✓ Reusing existing project 'Exto Instances': 363006148858609667
✓ Reusing machine user 'console-worker': 363006148900814851
✓ Granted console-worker IAM Owner
✓ Skipping client secret generation — console-worker already exists (use existing credentials)
✓ Reusing existing Console Portal app: appId=... clientId=...
✓ Set Zitadel label policy to light theme
✓ Reusing existing webhook target 'console-profile-sync': ...
Bootstrap complete. Values written to .bootstrap.env4. What bootstrap creates
| Resource | Purpose |
|---|---|
| Exto Instances project | Groups all instance OIDC apps under one project |
| console-worker machine user | Service account with IAM_OWNER for Zitadel Management API calls. Access Token Type must be JWT — opaque tokens will fail JWKS validation on instances. |
| Console Portal OIDC app | PKCE SPA app for the Console frontend (no client secret — public client) |
| Light theme label policy | Matches Console's login page styling |
| console-profile-sync webhook target | REST webhook with JSON payload type for user profile sync events |
| Webhook executions | Triggers on user.human.profile.changed, user.human.email.changed, user.deactivated, user.reactivated |
5. Bootstrap output
Results are written to .bootstrap.env:
env
ZITADEL_ISSUER=http://localhost:8080
ZITADEL_API_URL=http://localhost:8080
ZITADEL_ADMIN_ORG_ID=<org-id>
EXTOID_PROJECT_ID=<project-id>
CONSOLE_SERVICE_CLIENT_ID=<client-id>
CONSOLE_SERVICE_CLIENT_SECRET=<secret>
CONSOLE_PORTAL_CLIENT_ID=<client-id>
ZITADEL_WEBHOOK_SECRET=<signing-key-from-zitadel>Copy these values into your deployment environment (.env, K8s secrets, etc.).
Note: On re-runs, CONSOLE_SERVICE_CLIENT_ID, CONSOLE_SERVICE_CLIENT_SECRET, and ZITADEL_WEBHOOK_SECRET are omitted if the resources already existed (the secret/key cannot be retrieved after initial creation).
6. Idempotency details
| Resource | First run | Re-run |
|---|---|---|
| Project | Created | Reused (matched by name) |
| Machine user | Created | Reused (matched by username) |
| Client credentials | Generated (rotates secret) | Skipped — avoids invalidating existing deployments |
| OIDC app | Created | Reused (matched by name + project) |
| Webhook target | Created (returns signing key) | Skipped — signing key cannot be retrieved |
| Webhook executions | Created | Skipped (target already exists) |
7. Delete the bootstrap admin
After Console is running and verified, delete the console-admin machine user from the Zitadel Console. The PAT is a privileged credential and is not needed at runtime.
Production bootstrap
bash
# .bootstrap-input.env for production
ZITADEL_URL=https://auth.yourdomain.com
ZITADEL_PAT=<prod-pat>
CONSOLE_URL=https://console.yourdomain.com
DEVELOPMENT_MODE=falseThen run make bootstrap as normal. DEVELOPMENT_MODE=false ensures OIDC apps require HTTPS redirect URIs.
File layout
.bootstrap-input.env # Bootstrap inputs (ZITADEL_URL, ZITADEL_PAT) — git-ignored
.bootstrap.env # Bootstrap outputs (copy into deployment env) — git-ignored
