Runxr is a completely free, self-hosted CI/CD app. It runs on your own laptop or server: your GitHub token, build logs, queue, and database stay with you.
There is no Runxr subscription, no paid database, and no paid hosting requirement. You only need a free GitHub account, Node.js, Python, Git, and Docker for the secure default build runner.
GitHub push → signed webhook → Runxr queue → isolated runner → live logs → result + optional alert
- You sign in with GitHub and choose a repository.
- Runxr installs a push webhook when a public URL is configured.
- A push creates a persistent build job.
- Your local runner clones the repository and executes
runxr.ymlin Docker. - The dashboard shows the live log and final result. Failed jobs can be retried.
- GitHub OAuth sign-in and repository connection
- Signed GitHub push webhooks
- SQLite-backed jobs, logs, teams, encrypted GitHub tokens, and audit history
- Live logs, retry controls, build status, and notifications
- Docker-first runner with CPU, memory, PID, and network limits
- Teams and shared repository visibility
- Free local setup, diagnostics, and in-app documentation
Install these free tools:
- Node.js 22 or newer for the API
- Python 3.10 or newer for the runner
- Git for cloning repositories
- Docker Desktop for isolated builds
- A free GitHub account and a GitHub OAuth App
Cloudflare Tunnel can expose your local app for free while testing webhooks. Its free quick URL changes whenever the tunnel restarts. A stable custom URL needs a domain you control, but Runxr itself remains free either way.
git clone https://github.com/furyengi/runxr.git
cd runxr
npm installnpm run setup
npm run doctornpm run setup creates the private .env file and generates unique secrets. Do not commit .env or share its contents.
The questions mean:
| Setting | What to enter |
|---|---|
| Allowed GitHub owner | Your GitHub username, for example furyengi. Leave blank only for local testing. |
| Public HTTPS URL | Your tunnel or deployed address. Leave blank until you expose Runxr. |
| OAuth callback URL | http://localhost:4000/auth/github/callback locally, or https://your-url/auth/github/callback when public. |
| Client ID / secret | Values from the GitHub OAuth App you create next. |
In GitHub, open Settings → Developer settings → OAuth Apps → New OAuth App.
- Application name:
Runxr on my laptop - Homepage URL:
http://localhost:4000(or your public URL) - Authorization callback URL: exactly the callback URL from your
.env
Create the app, generate a client secret, then run npm run setup again to save the Client ID and client secret. Each Runxr installation should use its own OAuth App and secrets.
npm test
npm run devOpen http://localhost:4000, select Connect GitHub, approve access, and you will arrive at the dashboard.
Open a second terminal from the Runxr project:
cd runxr-runner
python -m pip install -r runner/requirements.txt
docker build -t runxr-job-executor -f Dockerfile.job ..
python agent.pyThe runner automatically reads the parent project’s .env. It uses Docker by default. Check it with:
npm run doctorIf Docker is unavailable, you may set RUNXR_EXECUTION_MODE=host only for repositories you fully trust. Host mode runs repository commands directly on the computer.
- In the dashboard, select Browse repositories.
- Select Connect next to a repository you own or can administer.
- Add a
runxr.ymlfile to that repository:
steps:
- name: Install
run: npm ci
- name: Test
run: npm test
- name: Build
run: npm run build- Push the file after you finish the webhook setup below.
The runner executes steps in order. A non-zero command stops the job and marks it as failed.
GitHub must reach your Runxr API. For a free temporary URL, install cloudflared and run this in another terminal:
cloudflared tunnel --url http://localhost:4000Copy the generated https://…trycloudflare.com URL, run npm run setup, and enter it as the public HTTPS URL. Use this callback URL in your GitHub OAuth App:
https://your-trycloudflare-url/auth/github/callback
Restart npm run dev, sign in again, then reconnect the repository in the dashboard. Runxr will attempt to create the push webhook automatically.
If you create it manually in GitHub instead, use:
- Payload URL:
https://your-public-url/webhook/github - Content type:
application/json - Secret: the value of
RUNXR_WEBHOOK_SECRETin.env - Events: Just the push event
Keep the API, runner, and tunnel processes running while you use Runxr. The free quick-tunnel address changes after a restart, so update .env, the OAuth callback, and the repository webhook when it changes.
- Build jobs: status, live logs, and failed-job retry.
- Connected repositories: add repositories allowed to create Runxr jobs.
- Teams: create a local team, add GitHub usernames, and share a connected repository’s Runxr history.
- Notifications: reports whether optional Slack/email adapters are configured.
- Audit history: records sign-in, sign-out, repository connection, webhook-created jobs, and retries.
- Docs: opens this guide from either the landing page or dashboard.
Runxr works without notifications. To enable them, add these private values to .env and restart the API:
RUNXR_SLACK_WEBHOOK_URL=
RUNXR_RESEND_API_KEY=
RUNXR_NOTIFICATION_EMAIL=Slack and email providers determine whether their own free tiers are suitable for you. These integrations are optional; no Runxr feature requires a paid account.
- Back up
data/runxr.sqlitewhile the API is stopped. It contains the local job history, encrypted GitHub tokens, team data, and audit trail. - Keep
.envprivate. Rotate the GitHub OAuth secret and Runxr secrets if they are exposed. - Use Docker mode and keep
RUNXR_JOB_NETWORK=noneunless a trusted build genuinely needs network access. - Never connect or run untrusted repositories in host mode.
- After updating the code, run
npm install,npm test, andnpm run doctorbefore restarting the API and runner.
| Problem | Fix |
|---|---|
GitHub OAuth is not configured |
Run npm run setup and enter the OAuth Client ID and secret. |
| GitHub callback fails | Ensure the OAuth App callback URL exactly matches RUNXR_GITHUB_CALLBACK_URL. |
| Push does not create a job | Keep the tunnel/API running; reconnect the repository; verify its webhook URL and secret. |
| Runner cannot claim a job | Ensure the runner reads the same .env and RUNXR_RUNNER_TOKEN matches. |
| Docker runner fails | Start Docker Desktop and build the runxr-job-executor image. |
| Dashboard is empty | Sign in, connect a repository, start the runner, and push a commit containing runxr.yml. |
| Endpoint | Purpose |
|---|---|
GET /health |
Service health check. |
GET /auth/github |
Starts GitHub sign-in. |
POST /auth/logout |
Signs out. |
POST /webhook/github |
Receives signed push webhooks. |
GET /jobs |
Lists your/team jobs. |
POST /jobs/:id/retry |
Retries a failed job. |
GET /logs/:id/stream |
Streams live logs. |
GET /audit |
Lists your audit history. |
Runner-only endpoints require X-Runxr-Runner-Token.