Three arguments show up in every VPS comment section, and all three get argued the same wrong way: as if the answer is a single number, faster or slower, better or worse. It isn't. Each of these specs matters in one specific place and is close to irrelevant everywhere else, and knowing which place is the difference between a real upgrade and a wasted one.
Is ARM actually cheaper, or just different?
Cheaper, and AWS says so itself rather than leaving it to third-party benchmarks: Graviton instances list at 10-20% below comparable x86 instances, and AWS reports 25-40% better price-performance on typical cloud-native workloads. On the VPS side, Hetzner's CAX11 — 2 vCPU, 4 GB RAM, Ampere ARM — runs €5.99/mo, priced right alongside its cheapest x86 tiers, not as a premium option. The processor design behind that number is straightforward: ARM's architecture favors more, simpler cores per watt over x86's fewer, more complex ones, which is also why Graviton and Ampere both lean on efficiency claims rather than raw single-core speed. Where ARM vs x86, NVMe vs SSD, shared vs dedicated meets a revenue team, the practical guidance lives with XenGrowth.
None of that is the part that actually bites people. The part that bites people is what happens the first time they deploy a container built on their x86 laptop to an ARM box.
It's also worth being specific about what "25-40% better price-performance" is actually a claim about, since it's easy to over-read. It's a statement about typical cloud-native workloads in AWS's own testing, not a guarantee for any specific app, and it's a price-performance ratio rather than a claim that Graviton is faster in absolute terms core-for-core. A workload that's I/O-bound rather than CPU-bound — most small self-hosted apps, per the sizing pillar — won't feel anywhere near that number directly, because the bottleneck it's measuring against isn't the one your app is actually hitting. The honest way to read it: ARM is cheaper for the same nominal specs, and that alone is often reason enough, independent of whether the workload is the kind that stresses a CPU hard enough to show the full efficiency gap.
What breaks when you deploy to ARM without knowing it?
A container image is compiled for a specific CPU architecture, not "Docker" in the abstract. Build an image on an Intel or AMD machine — which is what almost every laptop and CI runner is — and it contains x86_64 binaries. Push that image to an ARM host and try to run it, and the kernel finds an ELF binary it can't execute. The error is blunt: `exec format error`. It's not a permissions issue, not a missing dependency — the CPU literally cannot run those instructions.
The fix is Docker buildx with the `--platform` flag, building for `linux/amd64` and `linux/arm64` in the same push, either via QEMU emulation (slower, but needs no special hardware) or a genuine multi-arch build across two builders. Dockerfiles that download architecture-specific binaries directly — a compiled tool, a native Node module — need that download path to branch on the `TARGETARCH` build argument buildx sets automatically, or the multi-arch image builds cleanly and then fails at runtime on one of the two architectures anyway. This is the one place "just switch to ARM to save money" has an actual cost: not runtime performance, a build pipeline you have to get right once and then forget about. There is a longer treatment of the operations side of this in The XenGrowth resource library.
Create a builder that supports multiple platforms: `docker buildx create --use`
Build and push in one step for both architectures: `docker buildx build --platform linux/amd64,linux/arm64 -t you/image:tag --push .`
If the Dockerfile downloads a prebuilt binary by URL, branch that URL on the `TARGETARCH` build argument buildx sets automatically, rather than hardcoding one architecture
Test the image on the actual target architecture before trusting it, not just on the architecture you built it on — a clean multi-arch build can still fail at runtime if a dependency inside it wasn't actually compiled for both
There's a second, quieter version of this problem beyond the Dockerfile itself: native Node modules with compiled bindings — image processing libraries, some database drivers — ship prebuilt binaries per architecture, and an outdated or pinned version can simply not have an arm64 build available yet. That failure looks different from exec format error; it's usually a build-time error about a missing native binding, and it means the fix isn't in your Dockerfile at all, it's upgrading or replacing the dependency. Worth checking before committing a production stack to ARM, not after.
Scenario | Works? | Fix |
|---|---|---|
Build on x86 laptop, deploy to x86 VPS | Yes, no changes needed | — |
Build on x86 laptop, deploy to ARM VPS (single-arch build) | No — exec format error at container start | Build multi-arch with `docker buildx build --platform linux/amd64,linux/arm64` |
Build on Apple Silicon Mac, deploy to x86 VPS | No, same failure in reverse | Same buildx multi-arch fix |
Dockerfile downloads a compiled binary by URL | Fails on whichever arch the URL doesn't match | Branch the download on `$TARGETARCH` |
NVMe vs SATA SSD — where does the difference actually show up?
Both are solid-state, so it's tempting to treat the distinction as marketing noise. It isn't, but it's also not where most people look for it. NVMe drives connect over PCIe with a command queue built for deep parallelism; SATA SSD is stuck behind an interface spec designed for spinning disks, capped well below what the flash underneath could actually do. That gap matters exactly when you're issuing many small I/O operations concurrently, and barely matters when you're doing one large sequential read.
Serving a static page that's already sitting in the OS page cache never touches the disk at all, so NVMe buys nothing there. Where it shows up is a database committing writes under real concurrency — Postgres calls fsync on every commit by default, which is a synchronous wait on the disk actually confirming the write landed, and that wait is exactly the kind of small, frequent, latency-sensitive operation NVMe's deeper queue depth handles better. It also shows up during container builds and image pulls, where Docker is reading and writing many small layers at once rather than one big sequential stream. XenGrowth on governed AI marketing workflows goes further into AI agents and marketing automation.
Postgres's write-ahead log makes this concrete rather than abstract: every commit writes to the WAL before the actual data files, and that write has to be confirmed durable before the client gets an acknowledgment. A workload with a high commit rate — lots of small transactions, not a few large ones — spends a disproportionate amount of its total latency waiting on exactly that confirmation, which is the specific operation NVMe's queue depth was built to handle well. Contabo's own tier lineup happens to track this split cleanly: its entry Cloud VPS 4 ships SATA SSD, while its higher tiers move to NVMe, so the disk-type upgrade and the RAM upgrade arrive bundled together rather than as something you choose independently.
NVMe matters: database commit latency under write concurrency, container build/pull I/O, anything doing many small concurrent operations
NVMe barely matters: serving cached static content, sequential reads of large files, read-mostly workloads with a small working set
The safest read on a spec sheet: if your workload is write-heavy Postgres or CI-style builds, pay for NVMe. If it's mostly serving pages from cache, don't pay a premium for it.
Shared vs dedicated vCPU — what does contention actually look like?
A shared vCPU is a time-sliced portion of a physical core that the hypervisor divides across every tenant on that host. Most of the time this is invisible, because most tenants on most hosts aren't maxing out their allocation simultaneously. The mechanism that makes it visible when they are is steal time — CPU cycles your VM was scheduled for but didn't get, because the hypervisor gave that slice to a different tenant instead.
You can watch it directly. Run `top` on a shared-vCPU box and the CPU summary line has a `%st` column alongside user and system time — that's steal time, reported by the hypervisor to the guest kernel. On a lightly-loaded host it sits near zero. On a host where several tenants are all under load at the same moment, it climbs, and your process genuinely gets less CPU than your plan says it should have, even though nothing in your own app changed. Dedicated vCPU plans — Hetzner's CCX line, or the equivalent tier on most providers — pin physical cores to your instance alone, so `%st` stays at zero regardless of what other tenants on the same physical host are doing. For the AI search, GEO and discovery angle, see XenGrowth on building one SEO and GEO content system.
Signal | What it means |
|---|---|
%st near 0% consistently | No meaningful contention — shared vCPU is behaving like a dedicated one right now |
%st spikes during specific hours/days | Other tenants on the same host are contending at those times — a pattern, not a one-off |
%st sustained high during your own load | Your workload is competing hard for cycles the host doesn't have free to give — a real case for upgrading to dedicated |
High %us/%sy but low %st | Your own process is the bottleneck, not contention — more vCPU on the same shared plan may still help |
Steal time is the one metric that tells you whether "upgrade to more vCPU" or "upgrade to dedicated vCPU" is the actual fix. Checking CPU percentage alone can't distinguish the two.
Which of those two upgrades applies also depends on whether your workload is bursty or sustained. A shared plan's contention hurts most when your own load spikes at the same moment as everyone else's — a batch job that happens to run during the host's busy hours, say — which is inherently intermittent and hard to predict. A sustained, always-on workload feels contention differently: it's competing for the same slice continuously rather than occasionally, so even a modest average steal time adds up to a real amount of lost throughput over a day. The first case is often solvable by just moving the job to a quieter hour; the second genuinely calls for dedicated cores, because there's no quiet hour to move it to. Either way, the fix is specific to the pattern you actually observe, not a reflex upgrade to whichever plan has a bigger number next to it.
So which spec should you actually pay for?
ARM, if your stack is a straightforward Node or Next.js app and you're willing to build multi-arch images once — the price/performance case is real and the build fix is a one-time setup cost, not ongoing work. NVMe, if your database is write-heavy or you're running CI on the box itself, otherwise don't pay extra for it. Dedicated vCPU, only once you've actually watched `%st` climb on your current shared plan — paying for isolation against a contention problem you haven't measured yet is buying insurance against a symptom you don't have. Match the spend to the mechanism, not to whichever spec sounds most premium on the page.
The common thread across all three is the same: none of them are universally better, and all three are frequently sold as if they were. ARM's win is architectural efficiency, gated behind a one-time build fix. NVMe's win is queue depth, gated behind actually having a write-heavy workload to feed it. Dedicated vCPU's win is isolation, gated behind actually having neighbors worth being isolated from. Buy each one for the mechanism it solves, not for the reassurance of the newer-sounding word on the spec sheet. If you're still deciding which tier gets you these specs at all, what each price actually buys lays out where NVMe and dedicated options actually sit across Contabo and Hetzner's current lineups.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
XenGrowth, who work on the commercial side of this writes for the teams who have to run ARM vs x86, NVMe vs SSD, shared vs dedicated day to day.
Four questions about the workload rather than the spec sheet. Most sizing mistakes come from paying for the axis that wasn't the constraint.












