Skip to content

cabeaulac/devx-github-runner

Repository files navigation

GitHub Actions Self-Hosted Runner Example

This repository demonstrates how to set up and run a self-hosted GitHub Actions runner using Docker on Amazon Linux 2023. It provides everything you need to run your own GitHub Actions runner locally or in your infrastructure.

Why Self-Hosted Runners?

Self-hosted runners give you:

  • Full control over the environment (OS, tools, dependencies)
  • Cost savings for high-volume CI/CD workloads
  • Access to local resources (databases, services, private networks)
  • Custom hardware (GPUs, specific CPU architectures)
  • Better performance for large repositories

What's Included

  • Amazon Linux 2023 Docker container with GitHub Actions runner
  • ARM64 support (also works on x86_64 with minor changes)
  • Makefile for easy runner management
  • Example workflow to test your runner
  • Complete documentation and troubleshooting guide

Architecture

┌─────────────────────────────────────────┐
│  GitHub Repository                      │
│  ├── .github/workflows/                 │
│  │   └── hello-world.yml (example)     │
│  └── Triggers workflow on PR/push      │
└─────────────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│  Self-Hosted Runner (Docker Container) │
│  ├── Amazon Linux 2023                 │
│  ├── GitHub Actions Runner v2.329.0    │
│  ├── Git, OpenSSL, jq, curl            │
│  └── Listens for jobs from GitHub      │
└─────────────────────────────────────────┘

Quick Start

Prerequisites

  1. GitHub account
  2. Docker Desktop or Docker Engine installed
  3. Git and Make installed

Step 1: Fork This Repository

Important: Fork this repository to your own GitHub account so the runner will work with your copy.

  1. Click the Fork button at the top of this repository
  2. This creates a copy under your account (e.g., https://github.com/YOUR_USERNAME/devx-github-runner)
  3. Clone your forked repository:
    git clone https://github.com/YOUR_USERNAME/devx-github-runner.git
    cd devx-github-runner

Step 2: Create a Personal Access Token

  1. Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
  2. Click "Generate new token (classic)"
  3. Select scopes: repo, workflow
  4. Copy the token (starts with gho_)

Step 3: Configure Your Runner

Create a .env file in the repository root:

GITHUB_URL=https://github.com/YOUR_USERNAME/devx-github-runner
GITHUB_TOKEN=gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Important:

  • Replace YOUR_USERNAME with your actual GitHub username
  • Your GitHub username will be automatically extracted from GITHUB_URL and added to the runner labels
  • The workflow uses ${{ github.actor }} to dynamically route jobs to runners labeled with the pusher's username

Optional environment variables:

CONTAINER_NAME=github-runner-al2023
RUNNER_NAME=my-custom-runner
RUNNER_LABELS=self-hosted,Linux,ARM64,amazonlinux  # Your username is automatically appended
RUNNER_WORKDIR=_work

Step 4: Start the Runner

# Start the runner
make start

# View logs
make logs

# Check status
make status

That's it! Your self-hosted runner is now online and ready to accept jobs.

Using the Runner in Workflows

The included example workflow (.github/workflows/hello-world.yml) demonstrates how to use your self-hosted runner:

name: Hello World

# Trigger on push to all branches except main, or manual dispatch
on:
  push:
    branches-ignore:
      - main
  workflow_dispatch:

jobs:
  hello-world:
    # Dynamically targets runner with label matching the GitHub username who pushed
    runs-on: [self-hosted, '${{ github.actor }}']

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Display timestamp
        run: echo "Workflow started at $(date)"

      - name: Say hello
        run: echo "Hello from self-hosted runner!"

How It Works

  • Dynamic Runner Selection: runs-on: [self-hosted, '${{ github.actor }}'] automatically routes jobs to the runner labeled with your GitHub username
  • Push Triggers: Runs on all branches except main (perfect for testing feature branches)
  • Manual Trigger: Can be triggered manually from the GitHub Actions UI
  • No PR Triggers: Pull requests don't trigger workflows for security reasons (prevents malicious code execution on self-hosted runners)

Testing Your Runner

  1. Create a feature branch:

    git checkout -b test-runner
  2. Make a small change and push:

    echo "# Test" >> test.txt
    git add test.txt
    git commit -m "Test self-hosted runner"
    git push origin test-runner
  3. Check the Actions tab in your GitHub repository to see the workflow running on your self-hosted runner!

Makefile Commands

Command Description
make help Show available commands
make start Start the runner
make stop Stop the runner
make restart Restart the runner
make logs View logs in follow mode
make logs-tail View last 50 lines of logs
make status Check runner and GitHub registration status
make build Rebuild the Docker image
make rebuild Rebuild and start the runner
make clean Stop and remove all containers and volumes

File Structure

.
├── .github/
│   └── workflows/
│       └── hello-world.yml      # Example workflow
├── Dockerfile                    # Runner container definition
├── docker-compose.yml            # Docker Compose configuration
├── entrypoint.sh                 # Container startup script
├── Makefile                      # Management commands
├── run-runner.sh                 # Helper script for multiple runners
├── .env                          # Your configuration (create this)
└── README.md                     # This file

How It Works

1. Authentication

This runner uses a Personal Access Token (PAT) instead of registration tokens due to a known GitHub Actions runner bug where registration tokens aren't properly handled.

The entrypoint.sh script configures the runner with:

./config.sh \
    --url "${GITHUB_URL}" \
    --pat "${GITHUB_TOKEN}" \
    --name "${RUNNER_NAME}" \
    --labels "${RUNNER_LABELS}" \
    --unattended \
    --replace

2. Container Lifecycle

  1. Build: Docker builds the Amazon Linux 2023 image with all dependencies
  2. Configure: Runner registers with GitHub using your PAT
  3. Run: Runner listens for workflow jobs
  4. Cleanup: On shutdown, runner automatically deregisters from GitHub

3. Job Execution

When a workflow triggers:

  1. GitHub sends the job to your runner
  2. Runner executes the workflow steps in isolation
  3. Results are reported back to GitHub
  4. Runner waits for the next job

Configuration Options

Environment Variables

Variable Description Default Required
GITHUB_URL Repository or org URL -
GITHUB_TOKEN Personal Access Token (PAT) -
RUNNER_LABELS Comma-separated labels (username auto-appended) self-hosted,Linux,ARM64,amazonlinux
CONTAINER_NAME Docker container name github-runner-al2023
RUNNER_NAME Name shown in GitHub docker-runner
RUNNER_WORKDIR Working directory _work

Runner Labels

Labels help target specific runners in workflows. Default labels:

  • self-hosted - Identifies this as a self-hosted runner
  • Linux - Operating system
  • ARM64 - Architecture (change to X64 for Intel/AMD)
  • amazonlinux - Distribution
  • Your GitHub username - Automatically extracted from GITHUB_URL and appended to labels

How it works:

  • The Makefile and run-runner.sh script automatically extract your GitHub username from GITHUB_URL
  • Your username is appended to RUNNER_LABELS when starting the runner
  • This enables the workflow's runs-on: [self-hosted, '${{ github.actor }}'] to route jobs to your runner

Example: If your GITHUB_URL=https://github.com/octocat/devx-github-runner, the runner will automatically be labeled with:

self-hosted,Linux,ARM64,amazonlinux,octocat

You can customize the base labels in .env:

RUNNER_LABELS=self-hosted,Linux,ARM64,amazonlinux,my-team
# Result: self-hosted,Linux,ARM64,amazonlinux,my-team,octocat

Advanced Usage

Docker-in-Docker

If your workflows need to build Docker images, uncomment the volume mount in docker-compose.yml:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock

And add Docker to the Dockerfile:

RUN dnf install -y docker
RUN usermod -aG docker runner

Multiple Runners

Run multiple runners using the included helper script:

# Start runner 1 (uses default .env settings)
./run-runner.sh 1

# Start runner 2
./run-runner.sh 2

# Start runner 3
./run-runner.sh 3

Each runner gets:

  • Unique container name: github-runner-1, github-runner-2, etc.
  • Unique runner name: runner-1, runner-2, etc.
  • Same GitHub URL and token from .env
  • Same labels from .env

Note: All runners share the same configuration from .env except for container and runner names.

Organization-Level Runners

To register a runner for an entire organization instead of a single repository:

GITHUB_URL=https://github.com/YOUR_ORG

Note: Your PAT needs admin:org scope for organization runners.

Custom Runner Version

To use a different runner version, edit docker-compose.yml:

build:
  args:
    RUNNER_VERSION: "2.320.0"  # Change this

Check latest versions at: https://github.com/actions/runner/releases

Troubleshooting

404 Error: "NotFound from 'POST https://api.github.com/actions/runner-registration'"

Cause: GitHub Actions runner bug where it doesn't properly handle registration tokens.

Solution: This configuration already uses PAT authentication (--pat) instead of registration tokens (--token) to work around this issue.

Amazon Linux Package Conflicts (curl-minimal vs curl)

Cause: Amazon Linux 2023 includes curl-minimal which conflicts with full curl.

Solution: Already fixed in Dockerfile with --allowerasing flag:

RUN dnf install -y --allowerasing curl ...

Runner Not Appearing in GitHub

  1. Check runner logs:

    make logs
  2. Verify PAT scopes:

    • Ensure your PAT has repo and workflow scopes
    • Regenerate PAT if needed
  3. Verify GITHUB_URL:

    cat .env
  4. Check runner status in GitHub:

    • Repository: Settings → Actions → Runners
    • Organization: Settings → Actions → Runners

Container Keeps Restarting

  1. Check logs for errors:

    docker logs github-runner-al2023
  2. Verify .env file exists:

    ls -la .env
  3. Test configuration:

    make stop
    docker compose up  # Run in foreground to see errors

Runner Not Picking Up Jobs

  1. Verify workflow syntax:

    runs-on: [self-hosted, amazonlinux]  # Correct
    runs-on: ubuntu-latest               # Wrong - uses GitHub-hosted
  2. Check runner labels match:

    make status
  3. Ensure runner is online:

    • Check in GitHub UI: Repository → Settings → Actions → Runners

Security Considerations

PAT Security

  • Never commit .env file to git (already in .gitignore)
  • Rotate tokens regularly
  • Use minimal scopes (only repo and workflow)
  • Store securely in password manager

Runner Isolation

  • Runners execute untrusted code from pull requests
  • Consider using ephemeral runners for PR workflows
  • Use Docker-in-Docker carefully (security implications)
  • Limit runner access to sensitive resources

Network Security

  • Runners need outbound HTTPS (443) to GitHub
  • Consider firewall rules for production deployments
  • Use private networks when possible

Production Deployment

Recommended Practices

  1. Use dedicated infrastructure (EC2, VM, physical server)
  2. Monitor runner health and uptime
  3. Implement log rotation for runner logs
  4. Set up alerting for runner failures
  5. Use configuration management (Ansible, Terraform)
  6. Implement auto-scaling for variable workloads

Systemd Service (Linux)

For production deployments, run as a systemd service:

[Unit]
Description=GitHub Actions Runner
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/devx-github-runner
ExecStart=/usr/bin/make start
ExecStop=/usr/bin/make stop

[Install]
WantedBy=multi-user.target

Health Checks

Monitor runner health:

# Check if container is running
docker ps | grep github-runner

# Check runner status in GitHub
make status

# View recent logs
make logs-tail

FIPS Compliance

This runner uses FIPS-validated cryptography:

  • TLS Implementation: native-tls with OpenSSL (not rustls)
  • Container Base: Amazon Linux 2023 with SYSTEMWIDECRYPTO=FIPS
  • All Dependencies: Configured with native-tls/openssl features

This is important for:

  • Federal compliance requirements (FISMA, FedRAMP)
  • Handling sensitive authentication data
  • Enterprise security policies

Cost Comparison

GitHub-Hosted Runners

  • Free tier: 2,000 minutes/month
  • After free tier: $0.008/minute (Linux)
  • Example: 10,000 minutes/month = $64/month

Self-Hosted Runners

  • Initial cost: Server/VM infrastructure
  • Runtime cost: $0 (after infrastructure)
  • Example: Run unlimited builds on existing hardware

Self-hosted runners typically break even at 5,000-10,000 minutes/month.

Learning Resources

Official Documentation

Related Projects

Community

Contributing

This is an example repository for educational purposes.

To use this tutorial:

  1. Fork this repository to your own GitHub account
  2. Follow the Quick Start guide with your forked copy
  3. Each user should have their own fork and runner

To contribute improvements:

  • Submit issues for bugs or documentation improvements
  • Share your deployment experiences and best practices
  • Propose enhancements via pull requests

License

This example is provided as-is for educational purposes. Use at your own risk.

Support

For issues specific to this example:

  • Check the Troubleshooting section above
  • Review GitHub Actions runner documentation
  • Search GitHub Community discussions

For GitHub Actions support:

About

Incorporate GitHub runners into your DevX and run your GH workflows locally

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published