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
  • curlsudo apt install curl / sudo yum install curl
  • jqsudo apt install jq / sudo yum install jq
  • envsubst (from gettext) — sudo apt install gettext-base / sudo yum install gettext

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:

  1. Auto-discovers the VM's public IP for TURN config.
  2. Generates random TURN + relay auth secrets.
  3. Generates mTLS certificates for the management ↔ Dex gRPC channel (one-time setup; persisted under artifacts/grpc-certs/).
  4. 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.
  5. Wires every OPENZRO_AUTH_* variable to point at https://<your-domain>/dex (skip this step if you set OPENZRO_AUTH_OIDC_CONFIGURATION_ENDPOINT to use an external IdP).
  6. 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

  1. Open the dashboard URL.
  2. Click Sign in — you're redirected to the Dex login page.
  3. Enter the email + password the script printed in step 3.
  4. You land in the dashboard with admin permissions.
  5. 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.

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:

TypeNameContentCloudflare proxy
AopenzroYOUR.SERVER.IP.ADDRESSDNS only
CNAME*.openzroopenzro.example.comDNS 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's staticPasswords block, 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

Backup

Back up your openZro configuration and data to protect against data loss.

Upgrade

Upgrade your openZro installation to the latest version.

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, set OPENZRO_DEX_ADMIN_PASSWORD_BCRYPT in setup.env, and re-run ./configure.sh followed by docker compose restart dex. The next login uses the new password.

    If you can't recover that way, redeploy from scratch: docker compose down -v removes the volumes, the next configure.sh run 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, check artifacts/dex.config.yaml's staticClients section.

  • 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.yaml matches what the dashboard expects.

For more, see the Troubleshooting guide.