What this covers
How to get the Levelrail control plane (levelrail) running on a Linux host, confirm it works, and upgrade or remove it later. The node agent is levelrail-agent, and levelrail-cli is the client. All three are static Go binaries.
Want only the CLI, to control an instance from your laptop? Jump to Installing just the CLI. install.sh sets up the server, not a remote client.
install.sh
Recommended. One command provisions Docker, a systemd unit, and the control plane on a real Linux server.
Docker
Already running everything else as containers? Pull the published images instead.
Build from source
For contributors, unreleased commits, or trying Levelrail locally without a server.
Requirements
Confirm these before you provision a server.No official minimum has been benchmarked or published (see the roadmap). The sizes above are a starting point, not a hard requirement. The control plane itself checks no minimum.
install.sh preflight checks: root, architecture, OS, systemd, curl, Docker version, RAM, free disk and ports 80/443/8080. It stops on a failed check unless you pass --force. RAM below 1 GB only warns (LEVELRAIL_MIN_RAM_MB); free disk below 10 GB on the data directory’s filesystem fails (LEVELRAIL_MIN_DISK_GB).
After install: levelrail-cli doctor (GET /api/v1/system/doctor) checks Docker reachability, free disk (warns below 1 GiB by default), data-directory writability, and that ports 80/443 are free to bind. It checks no memory or CPU floor. If a port is blocked, see Domains and ingress: firewall.
Option 1: install.sh (recommended)
- Runs the preflight table and stops on a failed check (
--forcecontinues anyway). - Installs Docker via
get.docker.comif missing. - Downloads the newest release for
linux/amd64orlinux/arm64and verifies its SHA-256 checksum. - Installs
levelrail-clito/usr/local/binfrom the same release (skip withLEVELRAIL_INSTALL_CLI=0). - Writes a
levelrail.servicesystemd unit, starts it, and waits forGET /healthz. - Checks that ports 80 and 443 answer on the server’s public IP, and prints the
ufw/firewalldcommands to open them if not. - Prints the dashboard URL, the one-time setup token for the first admin, and a reminder to back up
<data dir>/master.key.
A missing
checksums.txt is a hard failure unless you set LEVELRAIL_SKIP_CHECKSUM=1. While no stable release exists, the installer picks the newest pre-release; choose explicitly with LEVELRAIL_CHANNEL=stable|beta. A release with no binary for your architecture is skipped with a warning. Some providers never route a server’s own public IP back to itself, so treat a failed reachability test as a hint, not proof.First sign-in

1
Open the setup link
Open one of the
http://<ip>:8080/login?setup=<token> links from the install summary. The public address is printed first, then private addresses. Container bridge addresses are hidden.8080 is only the default. If that port was taken, install.sh picks the next free one (LEVELRAIL_DASHBOARD_PORT) and prints the real one. Use the port from your own output.2
Create the admin account
After you create the admin account, the setup wizard opens and runs the server checks shown above.
3
Enable HTTPS
The dashboard shows a “connection is not encrypted” banner until you enable HTTPS. Once you have an
https:// dashboard URL, sign-in over plain HTTP is refused.- Dashboard
- CLI
- Own domain
Open the Domains page and click Enable HTTPS. It issues a real Let’s Encrypt certificate for
<dashed-ip>.sslip.io, no DNS needed. See Domains and ingress.Lost the setup token or the dashboard port?
Lost the setup token or the dashboard port?
Print the token again on the server. The dashboard port is also in the unit file:
Create the admin without the setup token
Create the admin without the setup token
Set
APP_ADMIN_USERNAME and APP_ADMIN_PASSWORD in the unit (via sudo systemctl edit levelrail) before the first start.The https URL broke and I am locked out
The https URL broke and I am locked out
Allow plain HTTP sign-in temporarily. Run
sudo systemctl edit levelrail, add the lines below, then restart:APP_EXPERIMENTAL, see experimental features.
Common scenarios
Optional environment variables
Optional environment variables
Check the server first
install.sh --check is a read-only readiness report. It changes nothing and runs without root (port owners are then only partly visible).
- Before install
- After install (CLI)
- After install (API)
Server already runs a proxy
Coolify, Traefik, nginx or Caddy on the same box already hold ports 80 and 443, so a default install would start but never serve or issue certificates. The installer checks this before it changes anything (ss, and docker ps --filter publish=<port> to name a container and image):
--no-coexist (or LEVELRAIL_COEXIST=0) to fail preflight instead.
The installer never prompts. Pass
--coexist (or LEVELRAIL_COEXIST=1) to choose the mode up front, for example to move a server whose ports are currently free:
--domain only fills in the next command, which the installer prints at the end:
sudo sh install.sh upgrade --coexist.
See Behind an existing proxy for the proxy side. Let’s Encrypt only validates on 80/443, so certificates for these domains are issued by your existing proxy, not Levelrail.
Where the installer's settings live
Where the installer's settings live
Every address the installer chooses (
APP_HTTP_ADDR, APP_INGRESS_HTTP_ADDR, APP_INGRESS_HTTPS_ADDR, and APP_PUBLIC_HOST when you set it) is written to a systemd drop-in, /etc/systemd/system/levelrail.service.d/10-install.conf, never into the main unit.install.sh upgradereads it back and does not rewrite it, so custom ports survive upgrades, including the socket-activation switch that regenerates the main unit.- Installs made before the drop-in existed are migrated on their next upgrade (ports are read from the old main unit).
- If the main unit no longer matches what the installer would write (hand edits or an older installer),
upgradewarns and, if it has to regenerate the unit, keeps a copy atlevelrail.service.pre-upgrade. - Keep your own overrides in a later-sorting drop-in such as
override.conf(systemctl edit levelrail).
Verifying release binaries
Each release publisheschecksums.txt (SHA-256 of every CLI, agent and control plane binary), a keyless cosign signature bundle for it (checksums.txt.sigstore.json), and a GitHub build provenance attestation for the binaries. Releases cut before signing was added carry no bundle.
install.sh always verifies the SHA-256 checksum and refuses to install on a mismatch. It also verifies the signature when cosign is installed and the release ships a bundle.
cosign is installed for you
cosign is installed for you
When
cosign is not on PATH, install and upgrade download a pinned cosign release (v3.1.3, linux/amd64 or linux/arm64) from the official sigstore GitHub releases, check it against a SHA-256 pinned inside install.sh, and place it at /usr/local/bin/cosign. An existing cosign is never touched.If the download fails (offline host, blocked GitHub, wrong checksum) the installer warns and carries on with checksum-only verification. Opt out with --no-cosign or LEVELRAIL_NO_COSIGN=1.To install it on its own, for example to clear the Settings > Updates warning (this subcommand does fail loudly if it cannot install):Verify a download by hand
Verify a download by hand
Option 2: Docker
Already running everything else as containers? See Docker fordocker run and docker-compose.yml examples.
Images (both linux/amd64 and linux/arm64, multi-arch):
ghcr.io/glincker/levelrail(control plane)ghcr.io/glincker/levelrail-agent(node agent)
:latest and :vX.Y only move on a stable tag; :beta moves on every prerelease tag. :edge moves only when someone manually triggers a build against main, not on every push. Pin an exact :vX.Y.Z tag for anything you care about staying still.
Verifying image signatures
Every image is signed keylessly with cosign via GitHub Actions OIDC (no long-lived signing key). Verify a pulled image against this repository’s release workflow:levelrail-agent and any tag from the table above.
Reading SBOM and provenance
Reading SBOM and provenance
SBOM (SPDX) and provenance (SLSA) are attached as build attestations, not the older
cosign attach-sbom format, so cosign download sbom does not find them. Read them with docker buildx imagetools inspect:Option 3: build from source
The path for contributors, anyone running an unreleased commit, or trying Levelrail on a laptop without a server. SeeCONTRIBUTING.md in the repository for branch and commit conventions and how to run the tests.
Requirements: Go 1.26 or newer, a running Docker daemon, and Node.js with npm only if you build the frontend yourself.
web/, embedded into the control plane binary at build time, so production runs no Node process. See web/README.md for the frontend commands.
:8080 by default. For a throwaway local instance, start it with APP_DEV_MODE=1: it creates a fixed dev/dev admin and fixed API tokens from dev-fixtures.yml so you can skip sign-up.
Installing just the CLI
levelrail-cli is a separate, small client binary for your own laptop or a CI runner, the same shape as aws or gh: install it locally, point it at a running instance, authenticate over the network, no SSH key and no inbound port on the server. The dashboard prints these same steps under Settings, CLI access, with a copy button on each command.

1
Install the binary
cosign is installed) the same way install.sh does, and installs to ~/.local/bin, no root needed. For a system-wide install, run with sudo and set LEVELRAIL_CLI_INSTALL_DIR=/usr/local/bin.2
Point it at your instance
3
Log in
- Device login (a person)
- API token (CI or scripts)
gh auth login and aws sso login use. Approving a code requires an already-authenticated dashboard session, so it inherits that account’s two-factor or passkey requirement. Nothing extra to configure.Other ways to install the CLI
Every release asset is listed in
checksums.txt, which is signed with cosign and has a build provenance attestation. Windows binaries are published from the release after v0.2.0-beta.15; before that, run the install script from WSL.
Verifying the install
install.sh already fails loudly if the control plane is not healthy within 60 seconds (LEVELRAIL_HEALTH_WAIT). Re-check at any time:
- Unauthenticated
- Authenticated
Upgrading
Pick the path that matches how you installed. The self-update (next section) is the safest, because it restores the database as well as the binary on failure.Check first, then upgrade
levelrail-cli upgrade (or Settings > Updates in the dashboard) runs read-only preflight checks. Nothing is applied unless you start or schedule an upgrade, or you turned on automatic upgrades (off by default, see Upgrade safely).
The CLI also takes a fresh control plane backup unless you pass
--no-backup, then prints the exact command to run. The API is GET /api/v1/updates/preflight.
Upgrade with automatic rollback
Settings > Updates > Upgrade to vX.Y.Z now (or Schedule upgrade) andlevelrail-cli upgrade --apply run the safe self-update. It:
- Downloads the release and checks its checksum and cosign signature.
- Snapshots the database.
- Tries the new release’s migrations on a copy of that snapshot.
- Swaps the binary, restarts, and waits for health.
--timeout (90 seconds by default), the previous binary and, when the schema changed, the pre-upgrade database snapshot come back automatically. Every attempt, with its step timeline, is kept on the Updates page.
sudo levelrail self-upgrade --to <version>. See Upgrade safely for the runbook and what to do when something fails.
Run the upgrade
- install.sh
- Docker
- Manual binary swap
10-install.conf drop-in, see Server already runs a proxy), systemctl edit overrides and data. It restarts the service and waits for it to come back healthy. If the new binary is not healthy within LEVELRAIL_HEALTH_WAIT seconds the previous binary is put back and started again.To pin a release instead of the latest:Older binary refuses to start
A binary older than the database exits with status 78 and prints which release last ran on the data and the exact commands to recover. The installer’s unit addsRestartPreventExitStatus=78, so systemd does not restart-loop it. Nothing is modified: migrations only go forward.
Run the release it names (LEVELRAIL_VERSION=vX.Y.Z ... install.sh upgrade) or restore a backup taken by the older release.
Rolling back
Before pending migrations run, the control plane snapshots its database aslevelrail-<timestamp>.db under the data directory’s backups folder. To go back after a bad upgrade, install the previous binary, stop the service, then:
.before-restore-<timestamp>. The master key is not part of a snapshot.
Uninstalling
- install.sh
- Docker
install.sh uninstall leaves levelrail-cli in place. Remove those separately if needed.
Getting help
- Ran into a specific error? Check Troubleshooting first.
- Bugs, questions, feature requests: open an issue on GitHub.
- Everything else: email support@levelrail.com.
Next steps
Getting started
Sign in and deploy your first app.
Docker
Run the control plane and agent as containers.
Upgrade safely
Preflight checks, scheduled upgrades and automatic rollback.
Behind an existing proxy
Run next to Coolify, Traefik, nginx or Caddy.