Self-Hosting Quickstart
openZro is open source and self-hosted only — there is no "openZro Cloud" SaaS. To run the platform, you bring a Linux host (or a Kubernetes cluster) and pick a deploy path that fits your infrastructure.
If you want to learn the architecture before installing, see How openZro works.
Pick your deploy path
Docker Compose (recommended)
One Linux VM, native systemd-equivalent via docker compose, embedded Dex IdP. ~10 minutes start to finish.
Kubernetes (Helm)
Multi-replica HA from day one, GitOps-friendly, embedded Dex via the bundled subchart.
Ansible (bare-metal)
Multiple Linux hosts via systemd, no container layer, optional cloud-LB integration.
The rest of this page covers the Docker Compose path. It's the fastest way to try openZro on a single VM.
Infrastructure requirements
- A Linux VM with at least 1 CPU and 2 GB of memory
- Public reachability on TCP 80 + 443 (dashboard / management API) and UDP 3478 (TURN — used when peers can't establish a direct WireGuard tunnel)
- A public domain that resolves to the VM (e.g.
openzro.example.com)
Software requirements
- Docker with the Compose v2 plugin
curl—sudo apt install curl/sudo yum install curljq—sudo apt install jq/sudo yum install jqenvsubst(fromgettext) —sudo apt install gettext-base/sudo yum install gettext
Docker Compose with embedded Dex (recommended)
The configure.sh script under infrastructure_files/ is the
ADR-0006-aligned install path: it provisions an embedded
Dex IdP alongside the management server
and wires the OIDC trust between them. No external IdP needed
on day one — operators add Google / GitHub / Microsoft / Keycloak
/ Okta / Authentik / generic OIDC connectors at runtime via the
dashboard's Settings → Identity Providers page.
1. Clone the repo
git clone https://github.com/openzro/openzro.git
cd openzro/infrastructure_files
2. Configure your environment
cp setup.env.example setup.env
$EDITOR setup.env
The minimum you need to set:
# Public dashboard / management host
OPENZRO_DOMAIN="openzro.example.com"
# Datastore — sqlite (default), postgres, or mysql
OPENZRO_STORE_CONFIG_ENGINE="sqlite"
Read the comments in setup.env.example for everything else
(TURN external IP, custom Dex admin email, store DSNs, etc.).
The rest has sane defaults.
3. Run configure.sh
./configure.sh
The script:
- Auto-discovers the VM's public IP for TURN config.
- Generates random TURN + relay auth secrets.
- Generates mTLS certificates for the management ↔ Dex gRPC
channel (one-time setup; persisted under
artifacts/grpc-certs/). - Generates the Dex admin email + password (random) if you didn't supply your own. Captures the plaintext password to stderr ONCE — copy it now, only the bcrypt hash is persisted.
- Wires every
OPENZRO_AUTH_*variable to point athttps://<your-domain>/dex(skip this step if you setOPENZRO_AUTH_OIDC_CONFIGURATION_ENDPOINTto use an external IdP). - Renders all templates (compose, management.json, dex.config.yaml,
turnserver.conf) into the
artifacts/directory.
Sample output you should see:
================================================================
Generated Dex admin credentials — capture these NOW. The
plaintext password is not stored anywhere; only its bcrypt
hash lands in dex.config.yaml.
Email: admin@openzro.example.com
Password:
Sign in at https://openzro.example.com/dex once the stack is up.
Rotate by re-running configure.sh with
OPENZRO_DEX_ADMIN_PASSWORD_BCRYPT set in setup.env.
================================================================
4. Bring the stack up
cd artifacts
docker compose up -d
docker compose ps
Wait ~30 s for Let's Encrypt to issue the cert (first run). Then:
- Dashboard:
https://openzro.example.com - Dex IdP:
https://openzro.example.com/dex
5. Sign in
- Open the dashboard URL.
- Click Sign in — you're redirected to the Dex login page.
- Enter the email + password the script printed in step 3.
- You land in the dashboard with admin permissions.
- From Settings → Identity Providers you can wire up external connectors (Google, GitHub, Microsoft, Keycloak, Authentik, Okta, generic OIDC) at runtime; from Settings → Users you add additional admins.
Save the bootstrap password before closing the terminal. The
plaintext is shown exactly once. To rotate, set
OPENZRO_DEX_ADMIN_PASSWORD_BCRYPT in setup.env (use
htpasswd -bnBC 10 "" <password> to generate the hash) and
re-run ./configure.sh.
Optional: external IdP instead of embedded Dex
If you'd rather front the deployment with your existing IdP (Auth0,
Authentik, Keycloak, Okta, Zitadel, generic OIDC), set in
setup.env:
OPENZRO_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://idp.example.com/.well-known/openid-configuration"
OPENZRO_AUTH_CLIENT_ID="openzro-dashboard"
OPENZRO_AUTH_AUDIENCE="openzro-dashboard"
configure.sh skips the embedded-Dex defaults when this URL is
set, fetches the discovery document, and renders the management
config to point at your IdP. Each provider has its own setup
page under Identity Providers.
Optional: CNAME for wildcard subdomains
If you'll later use the openZro Proxy to expose internal services
under subdomains (e.g. app.openzro.example.com), add a wildcard
on top of the A record:
| Type | Name | Content | Cloudflare proxy |
|---|---|---|---|
| A | openzro | YOUR.SERVER.IP.ADDRESS | DNS only |
| CNAME | *.openzro | openzro.example.com | DNS only |
Not required for the openZro core deployment — only matters when you're layering your own reverse proxy. See External Reverse Proxy.
Legacy: Zitadel-bundled install
For backward compatibility with installs that pre-date ADR-0006,
getting-started-with-zitadel.sh is still shipped. It provisions
Zitadel (instead of Dex) as the IdP and wires the OIDC trust
between them.
curl -fsSL \
https://github.com/openzro/openzro/releases/latest/download/getting-started-with-zitadel.sh \
| bash
Use this only if:
- You have an existing Zitadel deployment you want to consolidate
- Your tooling is Zitadel-specific (provisioning APIs, audit pipelines, etc.)
For new installs, prefer the configure.sh/Dex path above —
Zitadel here is just one of the many IdPs operators can connect
to (its own page is at
Identity Providers → Zitadel),
not a piece of openZro.
Add more users / connect IdPs
openZro's user model goes through whichever IdP is wired into the management server's OIDC trust:
- Embedded Dex (default) — manage local accounts in
dex.config.yaml'sstaticPasswordsblock, or wire upstream connectors (Google, GitHub, etc.) at runtime via the dashboard - External IdP — users live in your IdP; openZro federates via OIDC
Identity Providers
Connect Google, Microsoft, Okta, Keycloak, Authentik, generic OIDC, or Zitadel as connectors via the dashboard.
Disable local authentication
Lock the embedded Dex to a single external IdP — useful in compliance settings where local accounts are forbidden.
Maintenance
Scaling self-hosted deployments
Split a single-VM install into separate management, signal, and relay tiers as you grow.
Configuration files reference
Every file the install scripts generate, plus the variables in setup.env that drive them.
Troubleshoot
-
I lost the bootstrap admin password
The plaintext is only printed once by
configure.sh. Easiest recovery: generate a new bcrypt hash, setOPENZRO_DEX_ADMIN_PASSWORD_BCRYPTinsetup.env, and re-run./configure.shfollowed bydocker compose restart dex. The next login uses the new password.If you can't recover that way, redeploy from scratch:
docker compose down -vremoves the volumes, the nextconfigure.shrun provisions a fresh admin. -
A user can't sign in via the IdP
Verify the dashboard's redirect URI is whitelisted in the IdP configuration (it's
https://<your-domain>/auth). For embedded Dex, checkartifacts/dex.config.yaml'sstaticClientssection. -
OIDC connector configured but doesn't appear on login
Check Settings → Identity Providers — the connector must be enabled. For Dex, verify the connector ID in
dex.config.yamlmatches what the dashboard expects.
For more, see the Troubleshooting guide.