For the complete documentation index, see llms.txt. This page is also available as Markdown.

Self-Hosted Runners

Install the Lunar CI Agent on self-hosted runners to instrument CI/CD pipelines and collect metadata during builds, tests, scans, and deploys.

The Lunar CI Agent instruments CI/CD pipelines to collect metadata during builds, tests, scans, and deployments. It wraps your existing runner process, monitors execution, and triggers scripts at the right moments.

This page covers the most common setup: self-hosted runners (including GitHub Actions self-hosted runners). If you're using GitHub-hosted managed runners, see Managed Runners.

Prerequisites

You need an existing self-hosted runner infrastructure (e.g. GitHub Actions self-hosted runner). Lunar adds instrumentation on top of your existing setup — it does not manage the runner lifecycle.

Adding to an Existing Runner

1

Download and install the Lunar CLI

Download the Lunar CLI

Or via the command line:

curl -LO https://github.com/earthly/lunar-dist/releases/download/v2.5.0/lunar-linux-amd64
chmod +x lunar-linux-amd64 && sudo mv lunar-linux-amd64 /usr/local/bin/lunar

Replace the version above with the latest from the releases page.

The CI agent is no longer installed separately. The CLI fetches it on first use through your Lunar Hub (see the last step), so the only binary you install here is lunar.

2

Set the required environment variables

export LUNAR_CI_TYPE=github
export LUNAR_HUB_TOKEN=your_hub_token
export LUNAR_HUB_HOST=your_hub_host
export LUNAR_HUB_GRPC_PORT=your_grpc_port
export LUNAR_HUB_HTTP_PORT=your_http_port
export LUNAR_RUN_CMD=path_to_github_runner_run.sh

The agent auto-detects state, cache, and bundle directories based on the running user:

  • Root → system paths (/var/lib/lunar, /var/cache/lunar/git-repos, /var/tmp/lunar/...).

  • Non-root → user paths under $HOME/.lunar/.

Override any of them by setting LUNAR_STATE_DIR, LUNAR_GIT_CACHE_DIR, LUNAR_BUNDLE_DIR, LUNAR_SNIPPET_DIR, LUNAR_SCRIPT_LOG_DIR, LUNAR_BIN_DIR, or LUNAR_LOCK_DIR — useful for read-only rootfs runners, systemd units with ProtectHome=, or non-standard home directories.

3

Run the agent

lunar ci-tracer run

On first run, lunar ci-tracer run downloads the agent through your Hub, verifies it, caches it under LUNAR_BIN_DIR ($HOME/.lunar/bin by default), and then runs it. Subsequent runs reuse the cached binary and skip the download.

The Hub must be reachable the first time you run this on a host (or whenever the cache is empty). Once the agent binary is cached, the agent only needs the Hub for its normal runtime traffic.

Upgrading from an earlier agent install? Previous versions shipped a separate lunar-ci-agent binary that you downloaded and started yourself. That separate download no longer exists — install only the lunar CLI and start the agent with lunar ci-tracer run, which fetches the agent for you.

For production usage, run lunar ci-tracer run under a process supervisor such as systemd so it restarts automatically on failure. See Systemd Configuration for an example unit file.

Using a Custom Runner Image

If your runners are containerized, install the lunar CLI inside a Dockerfile and make lunar ci-tracer run the entrypoint. The agent binary is fetched on first boot through your Hub — the build host does not need Hub connectivity:

Dockerfile
FROM my-custom-runner-image:ubuntu-slim

ENV LUNAR_HUB_HOST=my.cool.host.com
ENV LUNAR_HUB_GRPC_PORT=443
ENV LUNAR_HUB_HTTP_PORT=443
ENV LUNAR_CI_TYPE=github
ENV LUNAR_RUN_CMD=/home/ubuntu/actions-runner/run.sh

# Replace /home/ubuntu with your runner user's home directory
ENV LUNAR_STATE_DIR=/home/ubuntu/.lunar/state
ENV LUNAR_GIT_CACHE_DIR=/home/ubuntu/.lunar/git-repos
ENV LUNAR_BUNDLE_DIR=/home/ubuntu/.lunar/bundles
ENV LUNAR_SNIPPET_DIR=/home/ubuntu/.lunar/snippets
ENV LUNAR_SCRIPT_LOG_DIR=/home/ubuntu/.lunar/scripts
ENV LUNAR_BIN_DIR=/home/ubuntu/.lunar/bin
ENV LUNAR_LOCK_DIR=/home/ubuntu/.lunar/lock

RUN curl -LO https://github.com/earthly/lunar-dist/releases/download/v2.5.0/lunar-linux-amd64 && \
    chmod +x lunar-linux-amd64 && mv lunar-linux-amd64 /usr/local/bin/lunar

ENTRYPOINT ["lunar", "ci-tracer", "run"]

On the first container boot, lunar ci-tracer run accesses your Lunar Hub to download and cache the agent under LUNAR_BIN_DIR, then runs it. Subsequent boots reuse the cached binary only if LUNAR_BIN_DIR is persisted across container restarts (e.g. a mounted volume) — otherwise each fresh container downloads the agent again on first boot.

Optional: warm the cache at build time. Run lunar ci-tracer install during the build to download and cache the agent without starting it. This requires the build host to reach the Hub. Pass the Hub token as a build secret so it never lands in an image layer:

The agent is cached in LUNAR_BIN_DIR, so it's baked into the image layer. Make sure LUNAR_BIN_DIR points at a path that stays in the image.

The GitHub Actions runner may not work correctly when run as root. See GitHub's self-hosted runner documentation for details.

Bake everything into the image except secrets. Pass the Hub token at runtime:

Or in Kubernetes, reference a Secret:

For all environment variable details, see the Configuration Reference.


Running sudo and setuid binaries in traced workflows

To trace command execution, the agent installs a seccomp filter on the runner process. Linux only permits installing that filter when the process either holds the CAP_SYS_ADMIN capability or has the no_new_privs flag set. When the runner does not have CAP_SYS_ADMIN, the agent falls back to setting no_new_privs, which makes the kernel ignore the setuid bit. Non-root processes can then no longer escalate through setuid-root binaries — sudo, for example, refuses to run:

no_new_privs is inherited by every child process and can never be cleared, so this affects any unprivileged user your workflow switches to, not just the top-level runner user.

To keep sudo and other setuid binaries working under tracing, run the runner as root in a container that adds CAP_SYS_ADMIN. The agent detects the capability on the runner process and skips no_new_privs, installing the seccomp filter through the capability instead.

The runner must run as root for this to take effect: a capability added to a container is only held by a root (uid 0) process. A non-root runner process does not gain CAP_SYS_ADMIN from the container, so the agent still falls back to no_new_privs there.


Next Steps

Once installed, you can begin configuring:

For questions or enterprise onboarding:

Contact the Earthly team

Last updated