We Moved Our Production to UpCloud. Here Is What Sovereignty Actually Cost.
On this page
Plays from YouTube. Open on YouTube instead
On a Saturday evening at the end of August we promoted four databases, moved DNS, and watched every one of our customers’ agents reconnect to a platform that had not existed five weeks earlier. Ankra’s production now runs on UpCloud in Helsinki.
That is not a benchmark and it is not a spec sheet. It is our own uptime, our own invoice and our own data.
We have written about UpCloud twice before, both times as one contestant in a benchmark we ran on clusters we deleted the same week. This time we moved the thing we cannot afford to lose. Two and a half weeks in, here is what we can tell you.
Why we started
Ankra is a Swedish company and most of our customers are European platform teams. More and more of them arrive with a question that has nothing to do with speed. Where does the platform live, and who can compel the company that runs it?
For a product like ours the question is very concrete. Ankra holds the inventory of your clusters, the Git state of every stack, the audit trail of every deploy, and the credentials our agents use to reach your clusters. That is exactly what a compliance team means when they say “the platform”. If it sits with a provider whose parent company answers to a court outside the EU, every promise we make about where your data lives rests on a legal argument rather than on geography.
UpCloud is a Finnish company headquartered in Helsinki, with data centres across Europe, the United States and Asia-Pacific. The two zones we run in are both in Helsinki, on Finnish soil and under EU law. Choosing them turned “we believe this is fine” into “it is fine, and here is the company register”.
We wrote in July that sovereignty is one of the reasons Ankra exists. Running our own production somewhere else would have made that paragraph marketing. Running it here makes it a decision we have actually taken.
Sovereignty got us to the table. It did not decide the move on its own. The bill could not go up and the platform could not get slower. It turned out we could have all three, and the rest of this post is the receipts.
What moved
The cluster we left was one we had adopted rather than built. Three control planes and ten workers on Hetzner, running 12 stacks, 39 addons and 136 manifests, with a version skew between control plane and nodes that we were not proud of.
The real trigger sat underneath all of that. We were running six Postgres databases, and every one of them was a single instance with no replica. Sovereignty told us where to go, and those databases told us not to wait, because sooner or later one of them would have made the decision for us.
| Old production on Hetzner | New production on UpCloud | |
|---|---|---|
| Built by | hand, then imported into Ankra | Ankra, through our own UpCloud provisioner |
| Control plane | 3 nodes | 3 nodes, 4 vCPU and 8 GB each |
| Workers | 10 | 7 general workers, 3 dedicated to databases |
| Postgres | 6 databases, no replicas | 6 databases, replicated |
| Secrets store | legacy Vault | OpenBao, three nodes |
| Storage | local disk | UpCloud network block storage, MaxIOPS tier |
| Public entry | one load balancer | two managed load balancers |
| Zones | one | Helsinki 1 live, Helsinki 2 on standby |
We set ourselves one rule. The new cluster had to come out strictly more grown up than the old one. Replicas wherever we had been running a single copy, current hardware, current Kubernetes, and backups we had actually restored from. At cutover the new cluster ran the same addons as the old one, with zero version mismatches.
Building the whole thing through the same product our customers use was the point. If our own provisioner could not build our production, it had no business building anyone else’s.
One small decision paid for itself many times over. We bumped the control plane from the 2 vCPU plan to 4 vCPU before the first replica synced. That is roughly 30 euros a month per node for API-server headroom, and it is the cheapest insurance we bought all summer.
The night itself
There was no big-bang weekend. The migration ran in six steps over five weeks. Bring up the target, converge configuration, replicate continuously, validate, cut over, then soak for a few weeks before switching the old cluster off.
For the last two weeks both clusters sat behind the same Cloudflare edge, so page reads were served from either side. The only thing the cutover had to move was the write path.
We gave ourselves 20 minutes for the window and 60 seconds for the write pause around the database promotion. DNS records went to a one-minute TTL a day ahead. And we defined the point of no return precisely. It was the first write accepted on the new cluster. Before that, every step reverses. After it, the only way is forward, because flipping DNS back would leave two databases believing they are the primary.
We had planned to flip on August 28. A pre-flight check found something we did not like. Our own CI pipeline was still deploying to the old cluster, and the new one had quietly fallen 192 builds behind. We postponed a day and made repointing the pipeline an explicit step.
On August 29 the databases were promoted, DNS moved, and the board came back clean. Zero conflicts, zero warnings, every customer agent reconnected. No rollback was needed.
We do not have a measured figure for the write pause, only the 60 second budget it stayed inside. That is why the tile above says “under a minute” and not a number.
Three things from the run-up will bite anyone moving a control plane, and none of them are about UpCloud.
- Get every certificate issued before the window. One endpoint could not get its certificate while DNS still pointed at the old provider, and the install loop it caused was silent for 28 hours. Issue certificates through a DNS challenge early, so TLS is off the critical path.
- “Ready” is not “caught up”. A Postgres replica reports ready long before it has replayed its backlog. We gated promotion on the replica’s own progress, not on whether its pod was green.
- Parked is not the same as off. We had set a replica count of zero on two services and assumed they were parked. Their charts read a different setting, and both were quietly running the whole time. A read-only database on the new side was the only thing that made dual-running safe, and the cutover removes exactly that protection.
What it costs
The number on the tile is what Ankra’s own Cloud Cost view reports for the cluster today. 1,898 euros a month for three control planes, ten workers, the fast storage under six databases and a message bus, and two managed load balancers. It is the same cluster we priced against Hetzner, OVHcloud and DigitalOcean in the Compare Providers walkthrough.
We are not going to publish a “we saved X percent” figure, because we cannot produce an honest one. The old cluster was hand-built and had no replicas. The new one has replicas everywhere. Most of the difference between the two invoices is resilience we chose to buy. What we can show is what a node costs, because that is what you would be pricing.
| UpCloud plan | vCPU and RAM | Per month | We use it for |
|---|---|---|---|
| PREMIUM-4xCPU-8GB | 4 and 8 GB | €54 | control planes |
| PREMIUM-8xCPU-16GB | 8 and 16 GB | about €164 | application workers |
| PREMIUM-8xCPU-32GB | 8 and 32 GB | €216 | database nodes |
| CLOUDNATIVE-8xCPU-32GB | 8 and 32 GB | €130 | not yet, see below |
The last row is the interesting one. UpCloud’s newer Cloud Native plans are roughly 40 percent cheaper than the Premium line for the same shape. We chose Premium anyway, on purpose. The Cloud Native plans have no local disk, and that path was untested in our own provisioner at the time. We were not going to debug it in the middle of a production move.
So that is a cost we carry by choice, and it is the first thing we will reclaim once the soak period is over. If you are pricing UpCloud fresh, price the Cloud Native line.
One more thing about the bill before you build a spreadsheet around it. Every plan comes with a bandwidth allowance rather than metering traffic from the first gigabyte, which matters for a platform whose whole job is talking to clusters on other providers.
Is it faster
We did not have to guess. The August benchmark series exists because we were making this decision, and the same suite ran on both the old production cluster and the new one as a gate before the cutover. A regression would have stopped the move.
If you want the absolute numbers, they are in round one. UpCloud posted 5.4 Gbit/s between nodes, limited by the receiving node’s CPU rather than by the network, and 100k random read IOPS with the two workers within one IOPS of each other. In round two, nothing in the hyperscaler entry tiers beat that network number.
The trade is the last bar, and we want to be plain about it. UpCloud’s block storage lives on the network, not in the machine. It survives the server it is attached to, it delivers exactly the IOPS it promises, and it is 6 to 13 times slower than local NVMe when you read or write one big file end to end.
For our databases that costs nothing we can measure. They do small random reads and lots of small committed writes, and both are fast. If your workload scans huge tables or restores multi-gigabyte backups all day, you will feel it. We knew, we measured, and we took the trade, because a database that survives its own server is worth more to us than a fast sequential read.
Where UpCloud let us down
Every provider post we write has this section, and it is the one people quote back at us. UpCloud is not exempt.
- Disks only grow offline. UpCloud’s storage driver refuses to expand a volume while it is attached to a node, and then reports success anyway, so the resize lands at some future restart you did not plan. A detached volume grows fine, so the choice is a short stop for the workload or, for a replicated database, rebuilding one instance at a time onto a bigger volume. That second route is what we did when our main database outgrew its disk. Plan for it.
- Private networks stay inside one zone. A Hetzner network zone or a VPC on the big clouds spans several data centres, but an UpCloud private network does not stretch from Helsinki 1 to Helsinki 2. That is why our standby is a second cluster with replication rather than one cluster across two zones. Ankra can build multi-zone UpCloud clusters over an encrypted mesh, and they are in closed beta for exactly this reason.
- Capacity is confirmed when you create, not before. We have had a zone decline a plan we were counting on. It resolves, but do not assume the plan you priced is the plan you get on the day.
- “Up” is not “healthy”. A database node that had wedged under load reported state “up” the whole time, and a restart call returned success in seconds because the API accepted a reboot the machine never carried out. Every cloud does this. It is still worth saying, because a dashboard that trusts the provider will lie to you at the worst possible moment.
- GPUs live in one zone. The B200 servers behind the token-cost post are in Helsinki 2 only.
The standby in the second zone
Helsinki 2 holds a continuously replicated copy of the databases on one database node. Application compute stays off until a failover starts and is scaled up as the first step. UpCloud nodes reach Ready in three to six minutes, and six at once take about ten, which is what makes cold compute an acceptable trade against the roughly 1,000 euros a month it would cost to keep it warm.
The targets we set are one to two hours to recover unplanned, under 90 minutes when drilled, and five to fifteen minutes of data at risk.
Try the same decision on your own cluster
Nothing above required us to be Ankra. The loop we ran on ourselves is the one we recommend to everyone.
- Import the cluster you have today, on whatever provider it lives, with one Helm command.
- Open Compare Providers and price its real shape on UpCloud, Hetzner, OVHcloud and DigitalOcean from their own price books.
- Clone the stack to an UpCloud cluster, run the benchmark suite on both, and watch the Cloud Cost page for a week.
- Decide with three columns in front of you. Jurisdiction, invoice, and p99.
For us all three came out on the same side. That will not be true for everyone, and a platform that moves is what keeps the answer yours to change later.
Get started: Create a free account on Ankra and claim the UpCloud 500 euro trial.
Join our community: Slack
Follow us on: LinkedIn | GitHub
Contact us: hello@ankra.ai
Get the next post in your inbox
Related Posts
AWS vs GCP vs Hetzner: Six Clouds, One Kubernetes Benchmark
We ran the same k6, iperf3 and fio suite on EKS, GKE and Hetzner and lined the results up against UpCloud, OVH and DigitalOcean. The cheapest node in the test won more than it had any right to.
The Spec Sheet Is Not a Benchmark: Testing Kubernetes Clusters in 15 Minutes
Two identical cloud VMs measured 2.8x apart on CPU; one failed etcd's fsync bar. A 15-minute in-cluster benchmark suite catches this before production does.