Skip to main content
You do not need to build on Levelrail. Your CI builds and pushes an image to a registry, then tells Levelrail to deploy it and waits for the result. Levelrail only hosts.

The three steps

  1. Install the CLI in the job, or call the API directly. The installer is curl -fsSL https://levelrail.com/install-cli.sh | sh. Set LEVELRAIL_CLI_INSTALL_DIR to a directory you can write to (the default is /usr/local/bin for root and ~/.local/bin otherwise) and LEVELRAIL_VERSION=vX.Y.Z to pin a release.
  2. Authenticate with environment variables only. LEVELRAIL_API_URL is your control plane URL and LEVELRAIL_TOKEN is an API token with at least the deploy ability. LEVELRAIL_PROFILE picks a saved profile and is not needed in CI. The CLI also reads CI=true (or LEVELRAIL_CI=true) and then never prompts, and it does so by itself when stdin or stdout is not a terminal.
  3. Deploy by image, then wait. apps deploy returns as soon as the deploy is accepted. apps wait is what tells you whether the rollout worked.
apps deploy requires --image here; add --confirm to deploy into a protected environment. apps wait also takes --attempt-id and --poll-interval (default 3s). Create the token once and store it in your CI’s secret store:

Exit codes

Branch on these in scripts. A job running apps deploy then apps wait fails on any non-zero code, which is what you want.

Safe retries

apps deploy sends an Idempotency-Key, so re-running the same job applies the deploy once and replays the first result. The CLI derives the key from the app, the image and the run id. It reads only two sets of run variables: GITHUB_RUN_ID with GITHUB_RUN_ATTEMPT, and CI_PIPELINE_ID (set by GitLab CI). On any other CI it falls back to the current minute, so pass your own key from the CI’s build id:
The CLI retries HTTP 429 and 503 for requests that carry an idempotency key, waiting for Retry-After. LEVELRAIL_RETRY_MAX (default 3, 0 turns retry off) and LEVELRAIL_RETRY_MAX_WAIT (default 60s) bound it. See Deploy from your computer.

OIDC support

Deploy without a stored secret works on GitHub Actions only. The control plane accepts identity tokens only from https://token.actions.githubusercontent.com and matches them by GitHub repository, ref and environment (internal/api/ci_oidc.go). GitLab ID tokens, CircleCI OIDC and Buildkite OIDC are not accepted today, so the templates below use an API token. Supporting another CI would need a verifier for that issuer and a mapping from its claims to repository, ref and environment.

GitLab CI

Store LEVELRAIL_API_URL and LEVELRAIL_TOKEN under Settings > CI/CD > Variables, masked, and protected if you deploy only from protected branches.
CI_PIPELINE_ID is read for the idempotency key, so no extra flag is needed. The control plane must be able to pull $IMAGE, so add the registry credentials to the app first.

CircleCI

Store both variables in Project Settings > Environment Variables or in a context.

Jenkins

Create two Secret text credentials with the ids levelrail-api-url and levelrail-token under Manage Jenkins > Credentials.
The single-quoted sh strings keep Groovy from interpolating the secrets into the command line, and Jenkins masks the credential values in the log.

Buildkite

Keep the secrets in your agent environment hook, a secrets manager plugin, or Pipeline Settings. Do not put them in pipeline.yml.
$$ stops Buildkite from expanding the variable at upload time. LEVELRAIL_API_URL and LEVELRAIL_TOKEN must already be in the step’s environment.

Troubleshooting

Next steps

Last modified on October 23, 2026