Set up and manage self-hosted GitHub Actions runners on Linux and Windows, in WSL or Docker—with security checks, lifecycle controls, and a verification workflow generator.
Built with TypeScript and Commander.
- Why dgrunner?
- Security Model & Guardrails
- Prerequisites
- Installation
- Agent Skills
- Quick Start Guide
- CLI Command Reference
- Verifying Your Runner on GitHub
- Platform-Specific Notes & Troubleshooting
- CI/CD & npm Trusted Publishing
- Development & Testing
- License
The repository includes installable skills that teach agentic coding tools how to set up, operate, and troubleshoot dgrunner. Skills are compatible with Claude Code, Codex, and other agents supported by the skills CLI.
List the available skills:
npx skills add UpstandPlatform/dgrunner --listInstall all dgrunner skills for all supported agents:
npx skills add UpstandPlatform/dgrunner --allOr install the skills for Claude Code and Codex:
npx skills add UpstandPlatform/dgrunner --skill dgrunner-setup dgrunner-operations dgrunner-troubleshooting --agent claude-code codexThe skill definitions are also included in the npm package under skills/. Skills.sh installs them from this public GitHub repository with its CLI; npm installation of @upstand/dgrunner remains a separate step for the runner command itself.
Self-hosting GitHub Actions runners gives you bare-metal speed, custom hardware/GPU access, and zero compute minute limits. However, running arbitrary CI/CD code on personal workstations or company machines poses severe security risks if misconfigured.
dgrunner solves this by automating:
- Pre-flight security audits before any binary is installed.
- Automated package integrity verification (SHA-256 validation against official GitHub releases).
- Linux system service and Windows background process management.
- Docker integration with rootless isolation checks.
- Automated verification workflows to validate host, container, and security boundaries.
GitHub advises against using self-hosted runners on public repositories because pull requests from public forks can execute arbitrary code on the runner machine. dgrunner provides pre-flight checks and safe defaults; operators remain responsible for repository visibility and runner isolation:
- GitHub URL Validation
- The CLI accepts GitHub.com and GitHub Enterprise Cloud URLs. It cannot determine whether a repository is private; only register runners for trusted repositories and organizations.
- Unprivileged Execution Checks
- Blocks setup when running as root on Linux unless the operator explicitly overrides pre-flight checks with
--force; checks elevation on Windows.
- Blocks setup when running as root on Linux unless the operator explicitly overrides pre-flight checks with
- Docker Security Checks
dgrunner doctorreports rootful Docker and unauthenticated remote TCP exposure. Checks may be overridden with--force; rootless Docker is recommended for Linux hosts.
- Binary Tamper Protection (SHA-256)
- All runner binaries are downloaded directly from official GitHub releases and cryptographically verified against official SHA-256 checksums before extraction.
- Ephemeral Secrets
- Short-lived runner tokens are entered via masked prompts and are not logged or persisted by dgrunner. Native runner configuration passes the registration token to GitHub's configuration process as a command-line argument, so local process inspection may expose it briefly; avoid supplying tokens with
--token. Docker configuration sends the token over stdin.
- Short-lived runner tokens are entered via masked prompts and are not logged or persisted by dgrunner. Native runner configuration passes the registration token to GitHub's configuration process as a command-line argument, so local process inspection may expose it briefly; avoid supplying tokens with
- Filesystem Permission Hardening
- Directories and local metadata files are created with restricted POSIX permissions (
0700/0600).
- Directories and local metadata files are created with restricted POSIX permissions (
Before setting up a runner, ensure your system has:
| Dependency | Minimum Version | Notes |
|---|---|---|
| Node.js | >= 22.14.0 |
Node 22 LTS or Node 24 LTS recommended |
| Git | >= 2.25.0 |
Required for checkout steps (Git for Windows on Windows) |
| Docker | Engine / Desktop | Docker Desktop (Windows) or Rootless Docker (Linux) |
| Operating System | Linux / Windows | Linux (x64, arm64) or Windows (x64) |
You can install and use dgrunner in three ways:
Install @upstand/dgrunner globally on your machine using npm:
npm install -g @upstand/dgrunnerOnce installed globally, the dgrunner binary is available across your entire system:
dgrunner --help
dgrunner doctorTo update to the latest release:
npm update -g @upstand/dgrunnerTo uninstall:
npm uninstall -g @upstand/dgrunnerRun the latest release directly without a persistent global install:
# Launch interactive setup wizard
npx @upstand/dgrunner
# Run diagnostics
npx @upstand/dgrunner doctor
# View integration guide
npx @upstand/dgrunner guideIf you want to contribute, modify, or run the development build locally:
# 1. Clone repository
git clone https://github.com/UpstandPlatform/dgrunner.git
cd dgrunner
# 2. Install dependencies
npm install
# 3. Build bundle
npm run build
# 4. Link globally (makes 'dgrunner' available in your terminal)
npm linkRun the built-in diagnostic doctor to ensure your PC, Git, Docker, and network meet all requirements:
dgrunner doctor- Navigate to your private repository on GitHub.
- Click Settings ➔ Actions ➔ Runners.
- Click the green New self-hosted runner button.
- Copy the registration token shown under Configure (
--token <TOKEN>).
Launch the guided setup wizard:
dgrunnerFollow the prompts:
- Select Private Repository (or Organization).
- Enter your repository URL (e.g.
https://github.com/owner/repo). - Paste your short-lived registration token (hidden input).
- Confirm the runner name and labels.
dgrunnerwill download the official runner, verify its SHA-256 hash, register it with GitHub, and start it. Linux installs a system service; Windows native mode starts a detached process that does not auto-start after sign-out or reboot.
Generate the ready-to-use verification workflow:
dgrunner guide --generate-workflowPush .github/workflows/verify-runner.yml to your repository:
git add .github/workflows/verify-runner.yml
git commit -m "ci: verify self-hosted runner"
git pushRuns a battery of system diagnostics and security audits:
- Host OS and CPU architecture compatibility.
- User privilege isolation (warns on elevated Windows admin; errors on Linux root).
- Git version and PATH availability.
- Git Bash detection on Windows (to avoid WSL collisions).
- WSL command availability and installed WSL2 distributions when targeting WSL.
- Docker engine availability and daemon responsiveness.
- Docker rootless mode audit on Linux.
- Unauthenticated remote Docker TCP socket check.
- GitHub HTTPS API reachability and latency.
- Free disk space availability.
dgrunner doctor
dgrunner doctor --strict # Exits with code 1 if warnings or errors exist
dgrunner doctor --dir ~/runner # Check specific installation path
dgrunner doctor --env docker # Evaluate checks for a planned Docker runner
dgrunner doctor --env wsl # Check WSL2 prerequisites from WindowsFull automated setup or scripted unattended setup via flags.
When running dgrunner setup on Windows, you can choose between:
- Windows Native (Default)
- Runs natively on Windows host as a detached background process or foreground runner. It is not installed as a Windows service and does not auto-start after sign-out or reboot.
- Ideal for PowerShell scripts, .NET builds, and Windows-specific pipelines.
- Linux Container (via Docker)
- Spins up an authentic Linux runner inside Docker with your choice of distribution:
- Ubuntu 24.04 LTS (Noble) (Default / Recommended -
ghcr.io/actions/actions-runner:latest) - Ubuntu 22.04 LTS (Jammy) (
ghcr.io/actions/actions-runner:2.316.0) - Debian (Bookworm compatible) (
ghcr.io/actions/actions-runner:latest) - Custom Docker Image (Any custom organization runner image via
--image)
- Ubuntu 24.04 LTS (Noble) (Default / Recommended -
- Distro labels (
ubuntu,ubuntu-24.04,ubuntu-22.04,debian) are automatically injected into runner labels. - Runs genuine Linux/Bash jobs on your Windows machine with automatic restart (
--restart unless-stopped). - Does not mount the host Docker socket by default. Use
--docker-socketonly when workflows need it; access to that socket can grant control of the Docker host. - Preserves build caches across container restarts in a dedicated, runner-isolated volume. Re-registering or removing the runner clears its workspace volume.
- Spins up an authentic Linux runner inside Docker with your choice of distribution:
- WSL2 Linux Environment
- Automatically detects all installed WSL distributions (
Ubuntu,Debian, etc.) and allows selecting or installing a new distribution directly from Microsoft Store (Ubuntu-24.04,Debian,kali-linux,archlinux). - Dynamically identifies distribution details from
/etc/os-releaseand tags labels accordingly. dgrunnercan automatically transfer setup to your selected WSL2 distro or guide you step-by-step.
- Automatically detects all installed WSL distributions (
# Interactive wizard (prompts for target environment, Linux distro, repository, and token)
dgrunner setup
# Set up a Linux runner container via Docker with Ubuntu 24.04 LTS:
dgrunner setup --os linux --distro ubuntu-24.04
# Set up a Linux runner container with Ubuntu 22.04 LTS (Jammy):
dgrunner setup --env docker --distro ubuntu-22.04
# Set up a Docker runner with a custom runner image:
dgrunner setup --env docker --image my-org/custom-actions-runner:latest
# Set up additional Docker runners with separate containers and purpose labels:
dgrunner setup --env docker --container-name dgrunner-build \
--url https://github.com/owner/repo --name build-runner \
--labels self-hosted,linux,x64,docker,ubuntu-24.04,build
dgrunner setup --env docker --container-name dgrunner-gpu \
--url https://github.com/owner/repo --name gpu-runner \
--labels self-hosted,linux,x64,docker,ubuntu-24.04,gpu
# Set up inside a specific WSL2 distro on Windows:
dgrunner setup --env wsl --distro Ubuntu-24.04
# Unattended Linux Container setup via Docker (token may be prompted for interactively):
dgrunner setup \
--os linux \
--distro ubuntu-24.04 \
--url https://github.com/owner/repo \
--name my-pc-linux-docker
# dgrunner prompts for the token using hidden input
# Scripted Windows Native setup:
dgrunner setup \
--os windows \
--url https://github.com/owner/repo \
--name my-workstation \
--labels self-hosted,dgrunner,gpu
# dgrunner prompts for the token using hidden inputOptions:
-u, --url <url>: Target GitHub repository or organization URL.-t, --token <token>: Optional GitHub Actions registration token. Prefer the hidden prompt to avoid shell history and process-list exposure. Native setup briefly passes the token to GitHub's configuration process in argv.-n, --name <name>: Custom runner name (defaults to<hostname>-linux-dockerfor the default Docker container; additional Docker containers get a stable unique suffix).-l, --labels <labels>: Comma-separated custom labels (e.g.self-hosted,dgrunner,linux,x64,docker,ubuntu-24.04).-d, --dir <path>: Runner installation directory (defaults to~/.dgrunner/actions-runner).--os <os>: Target runner OS:windowsorlinux(on Windows,linuxlaunches Docker container mode).--env <env>: Target environment:windows,docker,wsl, ornative.--distro <distro>: Linux distribution preset (ubuntu-24.04,ubuntu-22.04,debian) or WSL distro name.--image <image>: Custom Docker runner container image (e.g.ghcr.io/actions/actions-runner:latest).--container-name <name>: Docker container name. Choose a distinct name for each Docker runner; the default remainsupstand-dgrunner-linuxfor compatibility.--docker-socket: Explicitly mount/var/run/docker.sockinto the container. Workflows can gain control over the Docker host through this socket.--force: Bypass critical pre-flight failures at your own risk.--no-service: Configure without immediately starting the runner. Windows mode is a background process, not an installed OS service.
Each Docker runner needs a unique GitHub runner name and Docker container name. dgrunner gives each additional container a stable unique default runner name, its own persistent workspace volume, and a separate metadata record, so managing or removing one runner does not affect the others. Existing installations using the default container name continue to work.
Use the runner name or container name to select an instance with lifecycle commands:
dgrunner status --runner gpu-runner
dgrunner logs --runner dgrunner-gpu --follow
dgrunner stop --runner gpu-runner
dgrunner start --runner dgrunner-gpu
dgrunner remove --runner gpu-runnerRoute workflows by the custom labels assigned during setup. For example, runs-on: [self-hosted, gpu] selects a runner carrying both labels. Labels route jobs but do not isolate jobs from the host or from other containers on the same Docker host. Each runner handles one job at a time; multiple runners can run jobs concurrently subject to available resources.
Inspects installed runner metadata, configuration, and background service state:
dgrunner statusSample Output (Docker Runner with Ubuntu 24.04 LTS):
=== dgrunner Status Dashboard ===
┌─ RUNNER STATUS ────────────────────────────────────────────┐
│ Installed: Yes │
│ Configured: Yes │
│ Environment: Linux Container (Docker) [Ubuntu 24.04 LTS] │
│ Container: upstand-dgrunner-linux │
│ Image: ghcr.io/actions/actions-runner:latest │
│ Runner Name: my-workstation-linux-docker │
│ Target URL: https://github.com/my-org/private-repo │
│ Labels: self-hosted, linux, x64, docker, ubuntu-24.04 │
│ Service: Active & Running (Docker Container) │
└─────────────────────────────────────────────────────────────┘
Manages the lifecycle of the persistent runner daemon:
# Start the runner service in the background
dgrunner start
# Start and stay attached in foreground
dgrunner start --attach
# Stop the runner service or attached foreground process
dgrunner stop
# Restart the runner service
dgrunner restartDirect shortcut to start the runner in the foreground and stream live GitHub Actions job outputs directly to your terminal:
dgrunner runPress Ctrl+C to cleanly detach and stop.
Lists all active GitHub Actions runner processes managed by dgrunner with PID, memory consumption, status, and target repository:
dgrunner psSample Output:
=== Active dgrunner Processes ===
PID PROCESS RUNNER NAME MEMORY STATUS TARGET
─────────────────────────────────────────────────────────────────────────────────────
29892 Runner.Listener dordoone-runner 84 MB Listening https://github.com/mhbdev/dordoone
Views and streams real-time console logs from the running background runner service:
# View last 50 lines of logs
dgrunner logs
# Follow logs in real-time (live stream)
dgrunner logs --follow
# View last 100 lines and follow
dgrunner logs -n 100 -fDocker logs honor --lines for both static output and follow mode. Native Linux logs are read from the systemd journal and may prompt for sudo access. Windows background logs include both stdout and stderr.
Automatically checks the npm registry for the latest stable release and upgrades @upstand/dgrunner:
# Upgrade to the latest stable release
dgrunner upgrade
# Using alias 'update'
dgrunner update
# Check for available updates without installing
dgrunner upgrade --check
# Force re-installation of the latest version
dgrunner upgrade --force
# Upgrade to a specific distribution tag (e.g. beta / next)
dgrunner upgrade --tag beta
# Specify package manager (auto-detected by default)
dgrunner upgrade --package-manager pnpmOptions:
-c, --check: Check for available updates without installing.-f, --force: Force re-installation even if already on the latest version.-t, --tag <tag>: Specify distribution tag (default:latest).--package-manager <pm>: Override package manager (npm,pnpm,yarn,bun).
Safely deregisters the runner from GitHub Actions and removes local service configuration:
dgrunner removePrompts for a short-lived removal token (obtainable from GitHub repo Settings ➔ Actions ➔ Runners ➔ Click Runner ➔ Remove).
Displays colorized step-by-step instructions on navigating GitHub and safely generating registration tokens:
# Display general guide
dgrunner guide
# Display organization-specific guide
dgrunner guide --scope org
# Generate test workflow file
dgrunner guide --generate-workflowWhen you run dgrunner guide --generate-workflow, it writes .github/workflows/verify-runner.yml to your repository:
name: Verify dgrunner Self-Hosted Runner
on:
workflow_dispatch:
push:
branches: [ main, master ]
jobs:
verify-host:
name: 1. Verify Host & Environment
runs-on: ["self-hosted", "dgrunner"]
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Print Runner & OS Details
shell: bash
run: |
echo "======================================"
echo "Runner Hostname: $(hostname)"
echo "Current User: $(whoami)"
echo "OS Details: $(uname -a 2>/dev/null || cmd.exe /c ver)"
echo "Workspace Dir: $GITHUB_WORKSPACE"
echo "Bash Executable: $(which bash 2>/dev/null || echo 'unknown')"
echo "======================================"
verify-docker:
name: 2. Verify Docker Container Execution
runs-on: ["self-hosted", "dgrunner"]
steps:
- name: Test Docker CLI & Daemon
shell: bash
run: |
echo "Checking Docker version..."
docker --version
echo "Running lightweight test container..."
docker run --rm alpine:latest echo "Hello from containerized workflow on $(hostname)!"
verify-security:
name: 3. Verify Security Boundaries
runs-on: ["self-hosted", "dgrunner"]
steps:
- name: Check Non-Root User Execution
shell: bash
run: |
if [ "$(id -u 2>/dev/null)" = "0" ]; then
echo "WARNING: Workflow is running as root on host!"
exit 1
else
echo "SUCCESS: Workflow running under unprivileged user."
fi
- name: Test Docker User Namespace / Isolation
shell: bash
run: |
docker run --rm alpine:latest id
echo "Docker container isolation verified."On Windows, GitHub Actions steps configured with shell: bash require Git Bash (C:\Program Files\Git\bin\bash.exe).
Warning
If Windows Subsystem for Linux (WSL) is enabled, Windows places C:\WINDOWS\system32\bash.exe in PATH. When GitHub runner steps invoke system32\bash.exe, it fails with No such file or directory because WSL bash receives Windows-style drive paths (C:\Users\...).
How dgrunner fixes this automatically:
dgrunner automatically discovers your Git Bash directory and writes it to ~/.dgrunner/actions-runner/.path and the runner process environment. This guarantees that Git Bash is prioritized over WSL bash for all workflow steps.
If you want to run Linux runners on Windows, you have two great options:
-
Option A: Linux Container via Docker (Zero WSL Config): Run directly from your Windows PowerShell or Command Prompt:
dgrunner setup --os linux
This spins up the official Linux runner container directly on Docker Desktop.
-
Option B: Native WSL2 Runner: If you prefer running natively inside your WSL2 distro (e.g. Ubuntu):
# Open WSL wsl # Run setup inside WSL npx @upstand/dgrunner setup
[!TIP] If your workflows use Docker steps inside WSL, ensure Docker Desktop WSL integration is toggled on: Docker Desktop Settings ➔ Resources ➔ WSL Integration ➔ Enable integration for your distro (e.g.
Ubuntu). Rundgrunner doctorinside WSL to verify your connection.
If a job fails with UnauthorizedAccessException or Permission denied for /home/runner/_work/_tool, the runner worker could not create its tool cache before normal workflow steps started. In Docker mode, this path is inside the runner container and backed by a runner-isolated named volume; it is not a VPS path. The volume persists across container restarts and is cleared when the runner is re-registered or removed.
The official runner image runs as the unprivileged runner account. If Docker created the named volume as root, repair only the mounted work directory from the host:
docker exec --user root upstand-dgrunner-linux chown -R runner:runner /home/runner/_work
docker exec --user root upstand-dgrunner-linux install -d -o runner -g runner -m 0755 /home/runner/_work/_toolThen verify the runner account can write there:
docker exec --user runner upstand-dgrunner-linux sh -lc 'test -w /home/runner/_work && test -w /home/runner/_work/_tool && echo writable'Current dgrunner startup repairs ownership of a newly initialized work volume with that same account-scoped ownership change. Later TaskCanceledException broker messages can follow a failed job as the runner disconnects and retries; they do not explain the earlier _tool permission denial.
To prevent workflow containers from accessing the host root filesystem, install Rootless Docker:
# 1. Install rootless prerequisites
sudo apt-get install -y uidmap dbus-user-session
# 2. Run the official rootless setup tool
dockerd-rootless-setuptool.sh install
# 3. Export Docker host environment variable in your profile
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sockThen run dgrunner doctor to verify rootless isolation.
If you manually kill or restart the runner, GitHub's broker server may hold the previous session open for 30–60 seconds, resulting in:
A session for this runner already exists.
Runner connect error: Error: Conflict. Retrying until reconnected.
This is expected behavior from GitHub Actions. The runner will automatically retry and reconnect as soon as GitHub drops the stale session. Alternatively, run dgrunner restart.
@upstand/dgrunner comes with pre-configured GitHub Actions workflows implementing npm Trusted Publishing (OIDC) and cryptographic package provenance:
-
Continuous Integration (
.github/workflows/ci.yml):- Runs on every push and PR across Ubuntu and Windows on Node.js 22.x and 24.x.
- Enforces strict typechecks, unit tests, bundle builds, and npm package integrity validation (
npm pack --dry-run).
-
Automated Release & Publishing (
.github/workflows/release.yml):- Triggers when a matching semver git tag is pushed (for example,
v2.0.0). - Tests and builds the package before a separate least-privilege npm OIDC publish job publishes the verified tarball with provenance. No static npm token is used.
- Creates the GitHub Release only after npm publishing succeeds and attaches the package tarball.
- Triggers when a matching semver git tag is pushed (for example,
To configure npm to publish without static tokens:
- Log into your account on npmjs.com.
- Go to package
@upstand/dgrunner(or create it under@upstand). - Click Settings ➔ Publishing Access ➔ Add Publishing Access ➔ GitHub Actions.
- Set the repository name (
UpstandPlatform/dgrunner) and specify the workflow file name (release.yml). - The release workflow checks that the pushed tag matches
package.json, then publishes the verified package with provenance.
# Run TypeScript type check
npm run typecheck
# Run Vitest test suite
npm test
# Build bundled distribution
npm run build