CrestVPS

self hosted github runner

CI and build runners

Builds that finish before you have switched tabs.

What we would deploy

€44.03/mo

€62.9030% annual

Hosted CI minutes are metered, queued and slow, and the cost curve gets ugly the moment a team is productive. A pair of dedicated-core instances with fast NVMe will out-build most hosted tiers and cost a fixed amount every month regardless of how often you push. Nested virtualisation is included, so Docker-in-Docker and KVM-based test suites work without a workaround.

Why this configuration

TORQUE gives you the core count, Gen5 NVMe gives you the artifact throughput, and nested virtualisation costs nothing. Register the runners against your forge and delete the hosted plan.

What we would deploy

PlanTORQUE T2
vCPU8 (dedicated)
RAM32 GB
Storage400 GB
ImageUbuntu Server 24.04 LTS

Sizing

Two dedicated cores and 4 GB per concurrent job is the floor; compiled languages want 4 cores and 8 GB. A 32-core TORQUE node therefore runs 8 heavy jobs or about 14 light ones, not 32. Scratch disk matters more than either — budget 30-50 GB per concurrent job on NVMe for image layers, dependency trees and build output. Add a core per node if jobs need nested virtualisation. Queue wait time, not job duration, tells you when to add a node.

Nested virtualisation, and when you need it

Most CI runs fine in a container. Three cases need a real hypervisor inside the guest: testing VM images and cloud-init, running Android or embedded emulators with KVM acceleration, and any job where an untrusted pull request executes code that must not reach the runner. TORQUE exposes SVM to the guest, so KVM works inside your VPS and the emulator runs at native speed rather than through software emulation, which is roughly an order of magnitude difference on Android test suites. The cost is one core of overhead for the outer hypervisor and slightly slower disk in the inner guest. Budget for both.

The artifact path is usually the bottleneck

Profile a slow pipeline and the compile step is rarely the largest bar. Pulling base images, restoring caches, uploading artifacts and pushing the result account for most wall-clock time in typical builds. Three fixes, in order of return: keep a registry pull-through cache on the same private VLAN so layers arrive over local network rather than the public internet; put scratch on NVMe so layer unpack is not seek-bound; and make the cache key precise, so you restore what you need instead of a monolithic archive. Dense Bergamo cores are cheap. Waiting on the network is not.

What goes wrong

  • Stacking overlayfs on overlayfs for Docker-in-Docker. Nested overlay drivers are slow and occasionally corrupt layers. Use a dedicated block device inside the job, or run the builder over nested KVM instead.
  • Never pruning. Dangling images, build caches and stale volumes fill the scratch NVMe in weeks, and the first symptom is a build failing on a step that has worked for a year. Prune on a timer, not on failure.
  • Letting cache transfer exceed build time. A 3 GB dependency cache pulled and pushed per job can cost more than compiling from scratch. Measure both, and keep the cache on the same private VLAN as the runners.

Tune the machine

  • Everything elseNested virtualisation (VMX/SVM) · Included
  • CPU schedulingPinned physical cores · €9
  • Storage and encryptionZFS with hourly snapshots · €7