Skip to content
Cloudj

VPS use cases

Self hosted GitHub Actions runner on a dedicated-CPU VPS

Set up a self hosted GitHub Actions runner on a VPS: install it as a service, lock it down for public repos, and add hourly runners for burst CI capacity.

Updated · 5 min read

A self hosted GitHub Actions runner is a machine you control that picks up jobs from your GitHub workflows instead of GitHub's hosted runners. Teams move to one for three reasons: builds need more CPU or RAM than the default runners offer, jobs need software or network access that is awkward to set up on every run, or CI minutes have become a line item worth managing. This guide sets up a runner on a Linux VPS step by step, explains the security rules that matter (especially for public repositories), and shows how hourly billing lets you add burst capacity without paying for idle machines all month.

Choosing the server for a runner

CI jobs are bursty and CPU-hungry. A compile or test suite pins every core for a few minutes, then the machine sits idle. That pattern makes CPU type the main decision:

  • Dedicated CPU gives you dedicated cores, so build times stay consistent from one run to the next. Steady, CPU-heavy work is exactly what dedicated cores are for, which is also why people run a crypto trading bot on a Singapore VPS or a forex VPS in London on them.
  • General purpose is a sensible default for mixed workloads: linting, unit tests, small Docker builds.
  • Shared ("Availability") is fine for light jobs such as deploy scripts or scheduled tasks.

On Cloudj you can pick from 30 ready-made plans per location or build your own mix of vCPU, RAM and disk, from 1 vCPU with 1 GB RAM up to 104 vCPU and 512 GB RAM. For builds, disk matters more than people expect: Docker layers, dependency caches and build artifacts fill 20 GB quickly, so size the disk up early. Exact prices appear in the console before you deploy, and the pricing page shows live rates.

Location matters less for CI than for user-facing apps, but it still counts. Put the runner near what it talks to most: your container registry, your artifact storage or the servers it deploys to. The VPS locations guide maps the 14 countries Cloudj offers on the cloud servers page.

Step by step: installing the self hosted GitHub Actions runner

These steps use Ubuntu or Debian. Start from a server that already has SSH keys and a firewall, following the new VPS security checklist. The runner only makes outbound HTTPS connections to GitHub, so you do not need to open any inbound port for it.

1. Create a dedicated user

Never run the runner as root. Create a user for it:

sudo adduser --disabled-password --gecos "" runner
sudo su - runner

2. Download the runner

In your repository (or organization) go to Settings, then Actions, then Runners, and choose New self-hosted runner with Linux and x64. GitHub shows the exact download commands with the current version and a short-lived registration token. They follow this shape:

mkdir actions-runner && cd actions-runner
curl -o actions-runner.tar.gz -L https://github.com/actions/runner/releases/download/v<version>/actions-runner-linux-x64-<version>.tar.gz
tar xzf ./actions-runner.tar.gz

Copy the version from GitHub's page rather than from this article, because the runner is updated often.

3. Install dependencies and register

Back as a sudo-capable user, install the runner's system dependencies, then register as the runner user:

sudo /home/runner/actions-runner/bin/installdependencies.sh
sudo su - runner
cd actions-runner
./config.sh --url https://github.com/OWNER/REPO --token <TOKEN> --name build-01 --labels linux,dedicated --unattended

The labels are how workflows target this machine.

4. Run it as a service

The runner ships with a helper that installs a systemd service:

cd /home/runner/actions-runner
sudo ./svc.sh install runner
sudo ./svc.sh start
sudo ./svc.sh status

The runner now survives reboots and appears as Idle in the Runners list.

5. Point a workflow at it

jobs:
  build:
    runs-on: [self-hosted, linux, dedicated]
    steps:
      - uses: actions/checkout@v4
      - run: make test

If your jobs build containers, install Docker with the Docker on a VPS guide. Be aware that adding the runner user to the docker group effectively gives it root on the host, which brings us to security.

Security: the part people skip

A runner executes whatever code a workflow tells it to. That is the whole point, and also the risk.

Do not attach a persistent self-hosted runner to a public repository. GitHub's own documentation warns against it: anyone can open a pull request from a fork, and a workflow triggered by that pull request could run their code on your machine, read anything the runner user can read, and leave something behind for the next job. If you must use self-hosted runners with a public repo, require approval for workflows from outside contributors, and use ephemeral runners.

Other practical rules:

  • Use ephemeral runners for untrusted or mixed workloads. Registering with --ephemeral makes the runner take exactly one job and then unregister, so nothing persists between jobs. Pair it with a fresh server or a clean container per job.
  • Keep secrets scoped. Use environment-level secrets with required reviewers for deploy jobs, so a test job cannot read production credentials.
  • Separate build and deploy. A runner that can SSH into production should not also build pull requests.
  • Restrict outbound access if you can. A firewall that only allows what jobs need limits what a compromised job can reach.
  • Patch the host. The runner updates itself; the operating system does not. Enable unattended security upgrades.

The full GitHub guidance is in the self-hosted runner documentation.

Burst capacity with hourly billing

Most teams need one or two runners most of the time and five or ten for a release week. Hourly billing fits that shape. Cloudj charges hourly servers from your account balance for as long as they exist, and on hourly billing traffic is charged per GB used.

One detail matters: an hourly server is charged while it exists, running or stopped, because its disk and IP stay reserved. Stopping a burst runner does not stop the bill. When the release is out, unregister the runner (./config.sh remove --token <TOKEN>) and delete the server.

To make bursts fast, prepare one runner with your toolchain, Docker images and caches, remove its registration, and save it as a custom image. New burst runners deploy from that image and only need the config.sh step. The trade-offs between the two billing modes are covered in hourly vs monthly VPS, and short-lived environments in general in hourly VPS for staging.

Your always-on base runners belong on a prepaid monthly term with included traffic, the same choice that suits any machine that must stay up all month, such as a forex VPS in New York.

FAQ

Do I need to open any ports for the runner?

No. The runner connects outbound to GitHub over HTTPS and long-polls for jobs. Keep inbound limited to SSH.

Can one VPS run several runners?

Yes. Install each in its own directory with its own name and service. Make sure CPU and RAM cover concurrent jobs, or they will slow each other down.

Can I run Windows builds?

Cloudj offers Windows servers, which use password login and include a monthly licence fee shown in the console before you deploy. GitHub provides a Windows runner package for that case.

What happens to the runner when the server reboots?

Because the runner is installed as a systemd service, it starts again automatically after a reboot or a resize and goes back to Idle in the Runners list.

Next step

Deploy a dedicated-CPU server in the Cloudj console, register it as a runner, and watch a few builds before you add more. When you need extra capacity for a busy week, spin up hourly runners from your custom image and delete them when you are done.

Deploy a server or order proxies

Pick a location in the console, see the live price, and pay from your balance.