Your laptop has 4 cores. Maybe 8 if you splurged. CI workflows run on shared GitHub Actions runners with unpredictable performance. A high-CPU VPS with 16 dedicated cores changes the math entirely — your Rust workspace compiles in 90 seconds instead of 12 minutes, your FFmpeg x265 encode runs in real-time instead of 4× slower, and your CI pipeline finishes before you've finished the next sip of coffee.
This guide turns a high-CPU VPS into a serious remote build server: distcc for distributed C/C++ compilation, bazel-remote for cache reuse across builds, FFmpeg pre-tuned for x265 and AV1 encoding, and the kernel-level setup that prevents 'noisy build' problems on a multi-tenant VPS. By the end your laptop will feel slow.
- A high-CPU VPS — at least 8 dedicated cores, NVMe storage
- SSH access from your laptop / CI runner
- Roughly 40 minutes
For a build server with the kernel tunings and toolchains pre-installed, see our High CPU VPS plans — 4 to 16 dedicated cores, modern Xeon/EPYC, FFmpeg with H.265/AV1 pre-compiled.
1. Why "dedicated cores" actually matters
Most cloud VPS providers oversubscribe CPUs. A "1 vCPU" on AWS, DigitalOcean, or Linode is a time-share of a physical core — when neighbours are busy, your vCPU stalls. The kernel reports this as "steal time" (run top and look for st%).
On CPU-intensive workloads — compilation, video encoding, scientific compute — steal time destroys throughput. Your 4-hour build becomes a 6-hour build. Your real-time FFmpeg encode falls behind.
Dedicated cores eliminate this. Your 16 cores are 16 physical threads, locked to your VPS, all the time. Run top on a busy build — st% stays at 0.0%. The difference is night and day for CPU-bound work.
2. Prepare the VPS
apt update && apt upgrade -y
apt install -y build-essential cmake ninja-build pkg-config \
git curl ca-certificates ufw htop iotop sysstat
ufw allow 22/tcp
ufw --force enable
# Non-root user
adduser builder
usermod -aG sudo builder
rsync --archive --chown=builder:builder ~/.ssh /home/builder
Verify you actually have dedicated cores:
# Watch steal time during a busy moment
top -d 1
# Or measure with mpstat
mpstat -P ALL 1 5
The %steal column should stay at 0.00. If it climbs, you're not on dedicated cores. Open a support ticket.
3. Install build toolchains
One-shot install for the major languages:
# GCC and Clang
apt install -y gcc-12 g++-12 clang lld
# Rust (via rustup, as the builder user)
su - builder
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
# Go
GOVER=1.22.3
curl -fsSL https://go.dev/dl/go${GOVER}.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
# Java (for Bazel / Gradle workloads)
sudo apt install -y openjdk-21-jdk-headless
# Node.js (for JS toolchains)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Python build deps
sudo apt install -y python3-pip python3-dev python3-venv
4. Parallel make + ninja
The simplest CPU win: tell your build system to actually use all cores.
Make:
# Use all cores
make -j$(nproc)
# Or set globally
echo 'export MAKEFLAGS="-j$(nproc)"' >> ~/.bashrc
Ninja uses all cores automatically. If a project supports both, use Ninja — it parallelises better and has less overhead.
CMake invocation:
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
For C++ specifically, also use lld (LLVM's linker) — significantly faster than GNU ld for large projects:
# Tell GCC/Clang to use lld
export LDFLAGS="-fuse-ld=lld"
# For CMake projects
cmake -B build -DCMAKE_LINKER_TYPE=LLD -DCMAKE_BUILD_TYPE=Release
5. distcc for distributed C/C++ builds
If your build is so big that a single 16-core VPS isn't enough, distcc lets you fan out compilation across multiple build servers. Useful for Chromium-class projects.
On the build server VPS:
sudo apt install -y distcc
# Edit /etc/default/distcc
sudo sed -i 's/^STARTDISTCC.*/STARTDISTCC="true"/' /etc/default/distcc
sudo sed -i 's/^ALLOWEDNETS.*/ALLOWEDNETS="10.0.0.0\/8 100.64.0.0\/10"/' /etc/default/distcc
sudo systemctl restart distcc
On your laptop (the build coordinator):
apt install -y distcc
# Point at the build server
export DISTCC_HOSTS="builder-vps-ip/16,lzo"
# Build through distcc
CC="distcc gcc" CXX="distcc g++" make -j32 # 2× nproc for distcc
For maximum throughput, combine with ccache for compiler-output caching. The combination ccache+distcc is the de-facto setup for serious C++ build farms.
6. bazel-remote for cache reuse
Bazel and Buck2 fans: a remote cache shared across your laptop, CI, and the build server VPS dramatically cuts rebuild time. Same artifact gets built once, downloaded everywhere.
# Install bazel-remote
GOPATH=$HOME/go go install github.com/buchgr/bazel-remote@latest
# Run with 100 GB cache
mkdir -p ~/cache
~/go/bin/bazel-remote --dir ~/cache --max_size 100 --grpc_address 0.0.0.0:9092 --http_address 0.0.0.0:9091
In your .bazelrc (laptop and CI):
build --remote_cache=grpc://builder-vps-ip:9092
build --remote_upload_local_results=true
First build populates the cache. Subsequent builds (yours, your colleague's, CI's) hit the cache and skip compilation entirely. Typical cache hit rate on stable codebases: 80–95% — meaning a 10-minute build runs in 30 seconds.
🐾 Build environment pre-installed
Our High CPU VPS plans ship with GCC 12, Clang, Rust, Go, JDK 21, Node LTS, and Python 3.12 pre-installed — plus FFmpeg with libx265 and SVT-AV1 compiled and ready. Skip the toolchain marathon.
See High CPU Plans →7. FFmpeg CPU encoding (x265, AV1)
FFmpeg with libx265 and SVT-AV1 is the gold standard for CPU-based video encoding when GPU isn't available (or when you care more about quality-per-bit than speed). Even more so on AV1 where CPU SVT-AV1 produces excellent quality.
Install the version with x265 and AV1 support:
# The Ubuntu default ffmpeg is fine
sudo apt install -y ffmpeg
# Verify support
ffmpeg -encoders 2>/dev/null | grep -E 'x265|av1|x264'
Real-world encoding commands:
# H.265 at "balanced" CRF 22 — good quality, decent size
ffmpeg -i input.mp4 -c:v libx265 -preset medium -crf 22 \
-c:a aac -b:a 192k -threads 0 output.mp4
# AV1 (slower, much smaller files for the same quality)
ffmpeg -i input.mp4 -c:v libsvtav1 -preset 6 -crf 30 \
-c:a aac -b:a 192k output.mkv
# H.264 (universal compatibility, larger files)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 20 \
-c:a aac -b:a 192k output.mp4
Speed comparison on a 16-core High CPU VPS (encoding a 1080p 5-minute clip):
| Codec / preset | Time | Output size | Quality |
|---|---|---|---|
| libx264 fast | 22s (13.6× real-time) | ~210 MB | Good |
| libx264 slow | 56s (5.4× real-time) | ~165 MB | Better |
| libx265 medium | 2m 10s (2.3× real-time) | ~110 MB | Best (per-bit) |
| libsvtav1 preset 6 | 3m 40s (1.4× real-time) | ~95 MB | Best-modern |
8. Rust and Go fast paths
Rust: parallel compilation is on by default; tune incremental rebuilds with sccache:
cargo install sccache
echo 'export RUSTC_WRAPPER=sccache' >> ~/.bashrc
# Build a project — first build populates sccache
cargo build --release
Subsequent cargo build calls hit sccache and skip recompilation of unchanged crates. On a Rust workspace with 50+ crates, this typically cuts incremental rebuild times by 70–90%.
Go: parallelism is automatic and very efficient. The main lever is the build cache, which Go manages itself in ~/.cache/go-build/. To share across CI and developers, mount this directory as a network drive or use go mod download + S3 caching.
9. Reference: benchmarks and tuning
Workload to plan mapping
| Workload | Recommended plan |
|---|---|
| Personal Rust/Go projects, CI runner | Starter ($7.99, 4 cores) |
| Small C++ project, occasional FFmpeg | Pro ($15.99, 8 cores) |
| Chromium/LLVM builds, video farm, ML training prep | Premium ($35.99, 16 cores) |
Single-thread vs multi-thread
Some workloads are stubbornly single-threaded — old build systems, certain compression algorithms, parts of Rust's compilation. Check your CPU's base + boost clocks. Modern Xeon Gold runs ~3.5 GHz boost; modern EPYC runs ~3.7 GHz. Within a generation, base clock matters more than core count for these workloads.
Hourly billing for batch workloads
If you only need the box for 50 hours/month — Friday-night render jobs, weekend ML experiments — ask about hourly billing. Pay only when running; spin down between sessions. Cost-effective for spiky workloads.
SSD/NVMe scratch space
Builds generate huge intermediate output. A Rust workspace can produce 10–30 GB of object files. Always build on local NVMe (the VPS root disk), not on network-mounted storage. The 3 GB/s read/write of NVMe vs 100 MB/s of remote storage makes a 10× difference in link times.
Tmpfs for scratch builds
For builds that produce many small files, mount a tmpfs and build in RAM:
sudo mount -t tmpfs -o size=16G,mode=0777 tmpfs /tmp/buildspace
cd /tmp/buildspace
# ... clone, build ...
Caveat: lose power and the build is gone. Fine for ephemeral CI work; not for in-progress development.
CPU governor
Some VPS providers default to a power-saving governor that under-clocks the CPU. Check and set to performance:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# Set to performance (won't persist on most VPS — but try)
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
FAQ
Why CPU-only video encoding when GPU is faster?
CPU encoders (x265, SVT-AV1) produce 20–40% smaller files at the same visual quality compared to GPU encoders (NVENC, QSV). For archival or distribution where storage / bandwidth costs matter, that's huge. GPU encoders win on live streaming (real-time matters) and high-volume short-form (where speed beats efficiency). For Plex/Jellyfin libraries, FFmpeg CPU is the better long-term choice.
Is hyper-threading 'dedicated cores'?
Depends on the provider. Some count each hyper-thread as a 'dedicated core' (cheap definition), some count physical cores only (honest definition). When in doubt, ask before buying — the difference is real for CPU-bound work. Our plans count physical cores.
Can I use this VPS as a GitHub Actions self-hosted runner?
Yes — that's a great use case. Register the runner on your repo, point GHA workflows at the self-hosted label, and your CI runs on dedicated cores instead of GitHub's shared infrastructure. Typical speedup: 3–5× for compute-heavy builds. The setup is documented at GitHub's docs.
What about ARM (Graviton, Ampere) for build servers?
ARM builds are competitive on price-performance for native ARM workloads. For cross-compilation or x86 emulation builds, x86_64 is still the better choice. Most of our High CPU plans are x86 (Xeon/EPYC); ask if you need ARM specifically.
Does CPU pinning help build performance?
On dedicated-core VPS plans, not noticeably — the kernel scheduler already does a good job and there's no contention with other tenants. CPU pinning helps when you're running multiple isolated workloads on one box and want to prevent cross-talk (e.g. a build container shouldn't share L3 cache with a database container).
Can I run distcc + bazel-remote on the same VPS?
Yes — they don't conflict. distcc uses port 3632 by default, bazel-remote uses 9091/9092. Both can run on the same machine, and combining them gives you the best of both worlds: distcc for cross-developer C++ compilation, bazel-remote for cache reuse on Bazel-based projects.