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.
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
- 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
┌─────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────┘
- GitHub account
- Docker Desktop or Docker Engine installed
- Git and Make installed
Important: Fork this repository to your own GitHub account so the runner will work with your copy.
- Click the Fork button at the top of this repository
- This creates a copy under your account (e.g.,
https://github.com/YOUR_USERNAME/devx-github-runner) - Clone your forked repository:
git clone https://github.com/YOUR_USERNAME/devx-github-runner.git cd devx-github-runner
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click "Generate new token (classic)"
- Select scopes:
repo,workflow - Copy the token (starts with
gho_)
Create a .env file in the repository root:
GITHUB_URL=https://github.com/YOUR_USERNAME/devx-github-runner
GITHUB_TOKEN=gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxImportant:
- Replace
YOUR_USERNAMEwith your actual GitHub username - Your GitHub username will be automatically extracted from
GITHUB_URLand 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# Start the runner
make start
# View logs
make logs
# Check status
make statusThat's it! Your self-hosted runner is now online and ready to accept jobs.
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!"- 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)
-
Create a feature branch:
git checkout -b test-runner
-
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
-
Check the Actions tab in your GitHub repository to see the workflow running on your self-hosted runner!
| 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 |
.
├── .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
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- Build: Docker builds the Amazon Linux 2023 image with all dependencies
- Configure: Runner registers with GitHub using your PAT
- Run: Runner listens for workflow jobs
- Cleanup: On shutdown, runner automatically deregisters from GitHub
When a workflow triggers:
- GitHub sends the job to your runner
- Runner executes the workflow steps in isolation
- Results are reported back to GitHub
- Runner waits for the next job
| 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 |
❌ |
Labels help target specific runners in workflows. Default labels:
self-hosted- Identifies this as a self-hosted runnerLinux- Operating systemARM64- Architecture (change toX64for Intel/AMD)amazonlinux- Distribution- Your GitHub username - Automatically extracted from
GITHUB_URLand appended to labels
How it works:
- The Makefile and
run-runner.shscript automatically extract your GitHub username fromGITHUB_URL - Your username is appended to
RUNNER_LABELSwhen 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,octocatIf your workflows need to build Docker images, uncomment the volume mount in docker-compose.yml:
volumes:
- /var/run/docker.sock:/var/run/docker.sockAnd add Docker to the Dockerfile:
RUN dnf install -y docker
RUN usermod -aG docker runnerRun 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 3Each 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.
To register a runner for an entire organization instead of a single repository:
GITHUB_URL=https://github.com/YOUR_ORGNote: Your PAT needs admin:org scope for organization runners.
To use a different runner version, edit docker-compose.yml:
build:
args:
RUNNER_VERSION: "2.320.0" # Change thisCheck latest versions at: https://github.com/actions/runner/releases
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.
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 ...-
Check runner logs:
make logs
-
Verify PAT scopes:
- Ensure your PAT has
repoandworkflowscopes - Regenerate PAT if needed
- Ensure your PAT has
-
Verify GITHUB_URL:
cat .env
-
Check runner status in GitHub:
- Repository: Settings → Actions → Runners
- Organization: Settings → Actions → Runners
-
Check logs for errors:
docker logs github-runner-al2023
-
Verify .env file exists:
ls -la .env
-
Test configuration:
make stop docker compose up # Run in foreground to see errors
-
Verify workflow syntax:
runs-on: [self-hosted, amazonlinux] # Correct runs-on: ubuntu-latest # Wrong - uses GitHub-hosted
-
Check runner labels match:
make status
-
Ensure runner is online:
- Check in GitHub UI: Repository → Settings → Actions → Runners
- Never commit
.envfile to git (already in.gitignore) - Rotate tokens regularly
- Use minimal scopes (only
repoandworkflow) - Store securely in password manager
- 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
- Runners need outbound HTTPS (443) to GitHub
- Consider firewall rules for production deployments
- Use private networks when possible
- Use dedicated infrastructure (EC2, VM, physical server)
- Monitor runner health and uptime
- Implement log rotation for runner logs
- Set up alerting for runner failures
- Use configuration management (Ansible, Terraform)
- Implement auto-scaling for variable workloads
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.targetMonitor runner health:
# Check if container is running
docker ps | grep github-runner
# Check runner status in GitHub
make status
# View recent logs
make logs-tailThis runner uses FIPS-validated cryptography:
- TLS Implementation:
native-tlswith OpenSSL (notrustls) - Container Base: Amazon Linux 2023 with
SYSTEMWIDECRYPTO=FIPS - All Dependencies: Configured with
native-tls/opensslfeatures
This is important for:
- Federal compliance requirements (FISMA, FedRAMP)
- Handling sensitive authentication data
- Enterprise security policies
- Free tier: 2,000 minutes/month
- After free tier: $0.008/minute (Linux)
- Example: 10,000 minutes/month = $64/month
- 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.
- actions/runner - Official runner code
- myoung34/docker-github-actions-runner - Alternative Docker implementation
This is an example repository for educational purposes.
To use this tutorial:
- Fork this repository to your own GitHub account
- Follow the Quick Start guide with your forked copy
- 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
This example is provided as-is for educational purposes. Use at your own risk.
For issues specific to this example:
- Check the Troubleshooting section above
- Review GitHub Actions runner documentation
- Search GitHub Community discussions
For GitHub Actions support: