Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sail

Stand up a pre-baked, self-hosted GitHub Actions runner on your own Debian box (Proxmox LXC, VM, or mini-PC) in two steps. Bake the image once (all the CI utils included), attach it to a repo or org with a single one-time token, and the workflows in that repo run on your own hardware.

No stored secrets, no env keys, no PAT. The only credential is the ephemeral registration token GitHub gives you, pasted once and consumed.

Why self-host a runner

Self-hosted runners are a first-class GitHub Actions feature. Reasons to run one on your own box:

  • Your own hardware: control cost and CPU/RAM, or reuse a machine you already keep on 24×7.
  • Access to local resources: jobs that need something only reachable from your own network (an internal service, a homelab box, a device, a NAS).
  • Heavy or custom toolchains: pre-install big dependencies once instead of on every run.
  • Browser / E2E testing: sail bakes Xvfb + Chromium's system libraries so headed-browser tests run on a display-less server, with no apt or sudo in CI.

How it works

┌──────────────────────────────────────────────┐
│     bake.sh: build the image, no secrets     │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│         register.sh: one-time token          │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│ self-hosted runner: Node + Xvfb + Playwright │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│        outbound connection to GitHub         │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│      GitHub delivers your workflow yml       │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│          runs on your own hardware           │
└──────────────────────────────────────────────┘

The runner uses GitHub's pull model: it makes an outbound connection to GitHub and waits for jobs. GitHub doesn't connect back, so it needs no inbound ports (standard for any self-hosted runner). Your "test set" is just a workflow yml committed to a repo; GitHub delivers it to the runner.

The two-step model (the whole UX)

1. Bake: turn a fresh Debian box into the generic image. No secrets.

sudo ./bake.sh                               # full image
sudo ./bake.sh --no-browser                  # lean image (skip the browser layer)
sudo PW_PACKAGE=playwright@X.Y.Z ./bake.sh   # resolve Chromium's libs with your pinned Playwright

The image has the runner agent, Node, pnpm, git, gh, unzip, xz-utils, procps, and gcc/libc6-dev/make (cgo, which go test -race needs). The browser layer adds Xvfb and Chromium's system libraries, but not the browser: each repo's CI installs its pinned build into the shared, runner-owned /opt/ms-playwright. Re-running bake.sh on a box with runners already registered adds PLAYWRIGHT_BROWSERS_PATH to each runner's .env and restarts only the runners whose .env changed.

On Proxmox, snapshot the container afterwards so future clones are instant:

pct template <ctid>            # on the host

2. Register: attach the image to a repo or org with a one-time token.

sudo ./register.sh --repo owner/name --labels self-hosted,my-runner,linux
sudo ./register.sh --org  your-org   --labels self-hosted,linux

Get the token from Settings → Actions → Runners → New self-hosted runner (repo) or Org Settings → Actions → Runners (org). It's short-lived and single-use. Omit --token and you'll be prompted (so it never hits your shell history). Re-point at a different repo later by re-running register with a fresh token; the baked image never changes.

Org-level is the "one runner, many repos" sweet spot: register once to the org and every repo in it can target the runner by label.

Deliver a test set

Copy examples/browser-canary.yml into a repo at .github/workflows/, set runs-on: to your label, and replace the run command with your test. Commit it and GitHub delivers it to the sail runner. The image already provides Node, pnpm, git, gh, unzip, a C toolchain, a preset DISPLAY=:99, and Chromium's system libraries. Install your pinned browser with playwright install chromium; the first run downloads it into the shared cache and later runs reuse it. Don't add --with-deps: the libraries are already baked, and jobs have no sudo.

On Proxmox, start to finish

# host: make the box (passwordless root, onboot=1 autostart)
CTID=9001 ./create-lxc.sh
pct enter 9001

# inside: copy sail in (scp/clone), then:
sudo ./bake.sh
sudo ./register.sh --org your-org --labels self-hosted,linux

Autostart is two layers: Proxmox onboot=1 brings the container up with the host; systemd inside brings the runner + Xvfb up with the container.

If sudo warns unable to resolve host, set the hostname from the host with pct set <ctid> --hostname <name> and restart the container. bake.sh also maps the hostname in /etc/hosts as a fallback.

Verify

sail status                          # runner service + xvfb health
getent hosts "$(hostname)"           # hostname resolves (no sudo warning)
curl -fsSL https://api.ipify.org     # the box's public IP, as a sanity check

The runner should read Idle/online in the repo/org runner list. Dispatch a workflow and watch it land:

gh workflow run <your-workflow>.yml && gh run watch

Security

  • Runs as a dedicated non-root user; keep this box free of deploy/cloud creds.
  • One purpose per label; don't reuse the runner for unrelated build/deploy.
  • Never run untrusted PR code on a self-hosted runner. In the repo/org: Settings → Actions → Fork pull request workflows → "Require approval for all outside collaborators." Single most important hardening step.

Files

File Runs on Purpose
bake.sh the box Build the generic image (no secrets)
register.sh the box Attach to a repo/org with a one-time token
sail the box sail bake / sail register / sail status
create-lxc.sh Proxmox host Make a Debian LXC (passwordless, onboot=1)
examples/browser-canary.yml your repo A test-set workflow to copy and edit

About

A batteries-included GitHub Actions runner image builder for Debian/Proxmox, optimized for browser-based CI workloads and homelab deployments.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages