Every growing platform eventually faces the same choice: stay on virtualized infrastructure, easy to provision and resize in small steps, or move onto hardware you control outright. We stayed on the first for a long time. This week we moved to the second — taking Canner's entire build and hosting platform off a fleet of virtualized servers and onto dedicated hardware we operate ourselves, with customers deploying the whole time.
Why VPS made sense
Virtualized servers were the right call for most of this platform's life. They're fast to provision, easy to resize, and someone else owns the physical hardware. For most workloads at most stages, that trade is a good one.
The cost shows up later. You're sharing a physical machine with tenants you can't see. CPU allocation is often a metered slice of a core, which makes burst performance inconsistent. And a lot of the operating-system-level isolation a modern platform depends on doesn't fully work inside a virtualized guest — even when everything runs without error.
A security control that wasn't doing anything
Part of how we isolate one customer's build from another relies on kernel-level network restrictions — the operating system blocking a process from reaching anything it shouldn't, rather than relying on application code to behave.
During our own post-migration verification builds on the new hardware, one failed resolving a domain name mid-install. Another failed reaching our internal database proxy. Neither had ever happened before.
The restriction we'd relied on had never actually been enforced on our previous virtualized infrastructure — the low-level kernel access it depends on generally isn't granted to a virtualized guest. It had been a no-op the entire time, harmless only because nothing had gone looking for the gap.
Dedicated hardware gave us that access, and the same configuration started enforcing for real — a little too bluntly, blocking a couple of things a build legitimately needs. We narrowed it and re-verified against a live rebuild the same day.
You can't discover this on a VPS. Confirming a security boundary actually holds requires the same access an attacker would need, and virtualized infrastructure often can't give you an honest answer either way.
The migration itself
Moving live compute, databases, and every running customer application between two physical environments, on a fixed window, with customers deploying up to the moment of cutover, is dozens of small reversible operations, not one big one. Three stood out.
Moving data is a physics problem first. Our first attempt at transferring customer application data — tens of gigabytes across hundreds of bundles — routed through a single intermediate connection. At that rate the transfer alone would have used most of the maintenance window. Sending it directly between the two machines instead cut the time by roughly ten times.
Matching version numbers isn't matching compatibility. A handful of applications depend on a specific language runtime. We installed what should have been the identical version on the new hardware, and several immediately crashed with a binary compatibility error. Two operating systems can ship the same version number of a runtime built against incompatible system libraries underneath. The fix was copying the exact runtime install from the old environment rather than trusting a fresh install of the same number.
Ownership resolves by name, not number.Every customer application runs under its own isolated system user. Recreating those users on new hardware assigns fresh internal ID numbers that don't match the old ones — which sounds like it should break every file's ownership. It doesn't, as long as every user is recreated with the same name and the file transfer matches by name rather than number. We verified this held for every file, for every tenant, before calling the migration complete.
One more worth naming: part of our platform automatically reconciles network routing on restart, but rebuilding a route and restarting the application behind it turned out to be two different jobs living in two different places. A number of applications came back with a route pointing at them and no process running. We caught it in verification, before it reached a customer, and it's now a standing check for the next migration.
Everything else — customer code, databases, custom domains, certificates, environment variables — moved without a single change, because none of it was written assuming particular hardware underneath.
VPS or dedicated?
Stay on a VPS if your workload is I/O-light, doesn't need consistent CPU under burst, and the simplicity of someone else racking the hardware outweighs squeezing out maximum performance. That's a large share of real production workloads.
Move to dedicated hardware when you're seeing inconsistent performance you can't explain from your own code, your compute cost is scaling faster than your usage, or your security model depends on kernel-level isolation you haven't actually verified holds. If you haven't tested it, you don't know.
The performance difference isn't subtle. Identical workloads, same code, same dependencies, no changes at all, ran dramatically faster on dedicated hardware than on the virtualized fleet — we'll publish the detailed numbers separately. It's the difference between sharing a CPU's attention with strangers and having the machine to yourself.
Most teams shouldn't have to make this call themselves, or find these gotchas at 2 a.m. during a maintenance window. That's what we built Canner to absorb on your behalf.