Custom GitHub Actions runner with Node.js 20 and Datadog CI pre-installed. This eliminates the need to install these tools in every workflow run.
- Base:
myoung34/github-runner:latest - Node.js: Version 20 LTS with npm
- Datadog CI: Pre-installed for deployment tracking and observability
- Docker: Access to Docker socket for containerized workflows
- Persistent Storage: Cached tools, npm packages, and pip packages
- Docker installed and running
- Docker Hub account (for pushing custom image)
- GitHub Personal Access Token with
admin:orgscope - Datadog API and App keys (optional, for observability)
Copy the environment template and fill in your values:
cp .env.example .envEdit .env with your actual credentials:
# Required
ORG_NAME=your-github-org
ACCESS_TOKEN=your_github_personal_access_token
RUNNER_NAME=your-runner-name
# Optional (for Datadog integration)
DD_API_KEY=your_datadog_api_key
DD_APP_KEY=your_datadog_app_key
DD_SITE=datadoghq.com
DD_SERVICE=github-runner
DD_ENV=productionBuild the custom runner image and push to Docker Hub:
./deploy.sh./deploy.sh [OPTIONS]
Options:
--version X.Y.Z Tag image with semantic version (in addition to 'latest')
--skip-push Build and test only, don't push to Docker Hub
--skip-tests Skip image verification tests
--help, -h Show this help message# Standard deployment
./deploy.sh
# Build with version tag
./deploy.sh --version 1.0.0
# Build locally without pushing
./deploy.sh --skip-push
# Quick build without tests
./deploy.sh --skip-tests# Pull latest image (if pushed to Docker Hub)
docker-compose pull
# Start the runner
docker-compose up -d
# Check logs
docker-compose logs -f runner
# Check runner status
docker-compose ps# Copy files to server
scp docker-compose.yml .env user@server:/path/to/runner/
# SSH to server
ssh user@server
# Navigate to runner directory
cd /path/to/runner/
# Pull and start
docker-compose pull
docker-compose up -d.
├── Dockerfile.runner # Custom runner image definition
├── docker-compose.yml # Production-ready compose file
├── deploy.sh # Build and deployment script
├── .env.example # Environment variable template
├── .env # Your actual credentials (gitignored)
└── README.md # This file
| Variable | Description | Default |
|---|---|---|
ORG_NAME |
GitHub organization name | Required |
ACCESS_TOKEN |
GitHub Personal Access Token | Required |
RUNNER_NAME |
Name for this runner instance | Required |
RUNNER_SCOPE |
Scope: org or repo |
org |
RUNNER_GROUP |
Runner group name | default |
LABELS |
Comma-separated labels | self-hosted,linux,x64,ubuntu,docker |
EPHEMERAL |
Delete runner after each job | false |
DISABLE_AUTO_UPDATE |
Disable automatic updates | false |
| Variable | Description | Default |
|---|---|---|
DD_API_KEY |
Datadog API key | Optional |
DD_APP_KEY |
Datadog application key | Optional |
DD_SITE |
Datadog site | datadoghq.com |
DD_SERVICE |
Service name in Datadog | github-runner |
DD_ENV |
Environment name | development |
The runner uses persistent volumes for caching and configuration:
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Docker socket
- /docker/appdata/github-runner/work:/tmp/runner/work # Work directory
- /docker/appdata/github-runner/tools:/opt/hostedtoolcache # Hosted tools
- /docker/appdata/github-runner/npm-cache:/root/.npm # npm cache
- /docker/appdata/github-runner/pip-cache:/root/.cache/pip # pip cache
- /docker/appdata/github-runner/config:/runner # Runner configThe runner includes a health check that monitors the Runner.Listener process:
- Interval: 30 seconds
- Timeout: 10 seconds
- Retries: 3
- Start Period: 60 seconds
Check runner health:
docker-compose ps
# or
docker inspect github-runner-prod | jq '.[0].State.Health'- Check logs:
docker-compose logs -f runner - Verify
ACCESS_TOKENhasadmin:orgscope - Verify
ORG_NAMEis correct - Check runner status in GitHub:
https://github.com/orgs/YOUR_ORG/settings/actions/runners
Ensure the runner has access to Docker socket:
# Check Docker group ID
getent group docker | cut -d: -f3
# Update docker-compose.yml group_add with correct Docker GIDVerify installations in the image:
docker run --rm quickstark/gh-runner:latest node --version
docker run --rm quickstark/gh-runner:latest npm --version
docker run --rm quickstark/gh-runner:latest datadog-ci versionThis is usually caused by missing config persistence. Verify the config volume is mounted:
docker-compose down
docker-compose up -d
docker-compose logs -f runnerTo update the runner with new tools or configurations:
- Modify
Dockerfile.runner - Rebuild and deploy:
./deploy.sh --version X.Y.Z - Pull and restart:
docker-compose pull && docker-compose up -d --force-recreate
- Never commit
.envfile - it contains sensitive credentials .envis already in.gitignore- Use GitHub Secrets for production deployments
- Rotate
ACCESS_TOKENregularly - Use least-privilege tokens (only required scopes)
This project uses the following base images:
- myoung34/github-runner - MIT License
For issues with:
- This setup: Open an issue in this repository
- Base runner image: See myoung34/docker-github-actions-runner
- GitHub Actions: See GitHub Actions Documentation
- Datadog CI: See Datadog CI Documentation