Install NebulaCtrl
Install the NebulaCtrl control plane on one Linux host with a single command, then sign in through your OpenID Connect provider as the owner.
You'll install the control plane, the service that records what runs where and tells your clusters to run it. It runs in Docker on one Linux host. When you finish, you can sign in at your public URL as the owner.
NebulaCtrl is in beta
Upgrades migrate your data in place, and a console update that doesn't come up healthy is rolled back automatically. The HTTP API and nebula.toml may still change before 1.0. The changelog announces every change.
Before you begin
You need:
- A Linux host, amd64 or arm64, with
curl, Docker and the Docker Compose plugin. The installer doesn't install Docker. The host must meet the requirements. - A public URL for the control plane, for example
https://nebula.example.com. The installer binds the control plane to127.0.0.1:8080and expects a reverse proxy to terminate TLS in front of it. Every cluster you connect dials this URL over HTTPS, so it needs a valid certificate and must be reachable from each cluster. - An OpenID Connect (OIDC) client at your identity provider. The issuer must serve
/.well-known/openid-configuration. The provider must return an email address and must not report it as unverified. NebulaCtrl has no local accounts. - A user that can run
docker, or passwordlesssudo. The installer needs write access to/opt/nebula, or passwordlesssudoto create it.
1. Register the redirect URI
In your identity provider, add this redirect URI to the OIDC client:
https://nebula.example.com/api/v1/auth/callbackReplace https://nebula.example.com with your public URL. The client uses the authorization code flow with the scopes openid profile email.
You see the redirect URI saved on the client in your provider's console.
2. Run the installer
In a terminal on the host, run:
curl -fsSL https://get.nebulactrl.dev | shThe installer asks for each value it doesn't have and shows a summary before it changes anything:
- Base URL (public, e.g. https://nebula.example.com)
- OIDC issuer
- OIDC client ID
- OIDC client secret (typed without echo)
On a first install it also asks for Sign-in button label, Database (postgres://...) (leave it blank to run the bundled PostgreSQL) and Listen address. Answer Apply? to continue.
You see seven numbered stages, then a box with the URL to open:
Open https://nebula.example.com
Sign in with id.example.com
Redirect URI to register with your OIDC provider:
https://nebula.example.com/api/v1/auth/callbackWhat the installer does
Each stage prints a check mark when it passes.
- Checking your system. Confirms Linux on amd64 or arm64,
curl, Docker with the Compose plugin (directly or through passwordlesssudo), a writable install directory, and a free listen port. A missing requirement stops the run before the installer writes any configuration. - Configuration. Collects the values, warns if the issuer doesn't answer discovery, and refuses to move an existing install to an older version. With
--yesit stops here if a required value is missing. - Pulling the image. Pulls
ghcr.io/nebulactrl/nebula:VERSION, resolves its digest and confirms the image reports that version. - Writing configuration. Writes
/opt/nebula/.env(mode600) and downloads/opt/nebula/deploy/compose.prod.yaml. It generates the master key and the database password once, and a console-updates token when the host can run the updater. It pins the image by digest. Settings you added below the marker line in.envsurvive every re-run. - Starting containers. Runs
docker composeas the projectnebula. The control plane container starts with a read-only filesystem, no Linux capabilities and no new privileges, and applies pending database migrations when it boots. - Waiting for the control plane to become healthy. Polls
/healthzon the listen address for up to 180 seconds. On failure it prints the last 40 log lines. - Setting up console updates. Copies the
nebulabinary out of the image to/opt/nebula/bin/nebulaand installs the systemd servicenebula-updater, which lets an owner update the control plane from the console. This needs systemd and root or passwordlesssudo. Without them the install is still complete, and the installer prints a warning with the reason.
Re-running the installer is safe. It keeps every setting, never replaces the master key or the database password, and changes nothing when there's nothing new to apply. When it does change .env, it first backs up the old file as .env.bak-TIMESTAMP.
Back up .env now
/opt/nebula/.env holds the master key. It seals every secret in the installation: service variables, registry passwords and other stored credentials. A database restored without the key is readable, but every sealed value in it is lost, and nobody can regenerate the key. Copy .env to a safe place before you do anything else.
To skip the prompts, pass every value up front and add --yes. A missing value then fails the run and lists what's missing, before anything is changed. A --oidc-client-secret flag is visible to every process on the host through ps, so pass the secret in the NEBULA_OIDC_CLIENT_SECRET environment variable:
read -rs NEBULA_OIDC_CLIENT_SECRET && export NEBULA_OIDC_CLIENT_SECRETcurl -fsSL https://get.nebulactrl.dev | sh -s -- \
--base-url https://nebula.example.com \
--oidc-issuer https://id.example.com \
--oidc-client-id nebula --yesEach OIDC, database and listen option resolves in this order: flag, environment variable, the value already in .env from a previous run, an interactive prompt, then the default. --dir, --version, --image and the remaining options resolve from the flag, then the environment variable, then the default.
| Flag | Environment variable | Default | Meaning |
|---|---|---|---|
--base-url URL | NEBULA_BASE_URL | none, required | Public URL operators reach the control plane at. |
--oidc-issuer URL | NEBULA_OIDC_ISSUER | none, required | OIDC issuer. |
--oidc-client-id ID | NEBULA_OIDC_CLIENT_ID | none, required | OIDC client ID. |
--oidc-client-secret SECRET | NEBULA_OIDC_CLIENT_SECRET | none, required | Prefer the environment variable or the prompt. |
--oidc-provider-name NAME | NEBULA_OIDC_PROVIDER_NAME | the issuer's host | Label on the sign-in button. |
--database-url URL | NEBULA_EXTERNAL_DATABASE_URL | bundled PostgreSQL | Use your own PostgreSQL. The installer starts no database and doesn't back it up. It needs TimescaleDB 2.13 or newer. |
--listen ADDR | NEBULA_HOST_LISTEN | 127.0.0.1:8080 | Host address to bind. Use 0.0.0.0:8080 to expose the control plane directly. |
--dir PATH | NEBULA_INSTALL_DIR | /opt/nebula | Install directory. |
--version X.Y.Z | NEBULA_VERSION | the version the script shipped with | Release to install. |
--allow-downgrade | NEBULA_ALLOW_DOWNGRADE=1 | off | Install an older version than the one running. Migrations only move forward, so this is refused without the flag. |
--image REF | NEBULA_IMAGE | ghcr.io/nebulactrl/nebula:VERSION | Use a mirrored or private image. |
--compose-file PATH | none | downloaded from the release | Use a local compose file, for hosts that can't reach get.nebulactrl.dev. |
--no-updater | NEBULA_NO_UPDATER=1 | off | Don't install the nebula-updater service. |
-y, --yes | NEBULA_YES=1 | off | Never prompt. |
--no-color | NO_COLOR (any value) | color on a terminal | Turn off color. |
NEBULA_GET_URL overrides the download host, for a mirror. The versioned installer https://get.nebulactrl.dev/vVERSION installs that release.
3. Sign in
Open your public URL and select Continue with PROVIDER, where PROVIDER is the sign-in button label you chose, which defaults to the issuer's host. After your provider authenticates you, you land in the console as the owner of the Default organization.
The first person to complete a sign-in becomes the owner, whoever they are. Sign in yourself before you share the URL. Everyone after that needs an invitation. A person without one sees "has no invitation to an organization on this installation" and is signed out. See Members and teams to invite people.
You see the setup wizard at Connect your first cluster, because the Default organization has no cluster yet.
Verify
Check that the control plane answers on the listen address:
curl -s http://127.0.0.1:8080/healthz{"status":"ok","components":{}}If the listen address isn't the default, use the address you chose. Any other status is unhealthy and names the failing components. Signing in at your public URL, as in step 3, confirms the reverse proxy and the OIDC client as well.