Cost comparison between gcp and hetzner configuration
TMS applications — GCP resource & cost inventory
Apps covered: tms-booking, tms-drivers, tms-feedback, tms-planner, tms-reports (plus tms-fleet, which shares the same infrastructure).
Projects covered:
attracs-services-195009 — compute, networking, Pub/Sub, MySQL (attracs-sql)
pg-databases-stable — PostgreSQL instance hosting most TMS databases
Billing window: last 30 days, 2026-05-26 → 2026-06-24.
Source: attracs-billing.attracs_billing_dataset.gcp_billing_export_resource_v1_01FE17_D9735C_415ACA
Currency: EUR. Net = gross cost + credits.
TL;DR
| Bucket |
Net EUR / 30d |
TMS-named resources in attracs-services-195009 |
2,771.36 |
attracs-sql (MySQL, hosts tms_integration_service + Superset for tms-reports) |
366.43 |
pg-1-stable + read replica + backups in pg-databases-stable (shared, 3 of ~30 DBs are TMS) |
2,024.11 |
| Conservative TMS-attributable total |
~5,162 |
Whole-project totals for context (attracs-services-195009 + pg-databases-stable) |
9,635.17 |
Per-app cost cannot be split cleanly — the 5 TMS apps share the same VMs, the same PostgreSQL instance, the same load balancer, and the same Spanner. Only Pub/Sub topics are app-named, so only Pub/Sub is split per-app below.
1. Compute & networking — attracs-services-195009
1.1 TMS backend VMs (shared by all 5 apps via path routing)
| Resource |
Type |
Status |
Service |
Net EUR / 30d |
| tms-confidential-instance-stable-1 |
n2d-standard-16 |
RUNNING (zone a) |
Compute + Confidential |
453.58 |
| tms-confidential-instance-stable-2 |
n2d-standard-16 |
RUNNING (zone b) |
Compute + Confidential |
456.44 |
| tms-confidential-instance-stable-3 |
n2d-standard-16 |
RUNNING (zone c) |
Compute + Confidential |
450.27 |
| tms-confidential-instance-stable-4 |
n2d-standard-16 |
TERMINATED |
Compute |
9.25 |
| tms-backend-5 |
n2-highcpu-16 |
TERMINATED |
Compute |
15.72 |
| tms-backend-6 |
n2-highcpu-16 |
TERMINATED |
Compute |
31.44 |
| attracs-tms-backends-multi-zone-2zdb |
(MIG VM) |
TERMINATED |
Compute |
15.72 |
| attracs-tms-backends-multi-zone-zq8q |
(MIG VM) |
TERMINATED |
Compute |
15.72 |
| tms-valhalla-1 |
e2-standard-4 |
TERMINATED |
Compute |
9.25 |
1.2 Reservations / committed-use backing the running VMs above
| Resource |
SKU |
Net EUR / 30d |
| tms-confidential-instance-stable-zone-a |
Reserved N2D AMD Core/RAM (Finland) |
557.99 |
| tms-confidential-instance-stable-zone-b |
Reserved N2D AMD Core/RAM (Finland) |
278.99 |
| tms-confidential-instance-stable-zone-c |
Reserved N2D AMD Core/RAM (Finland) |
278.99 |
1.3 External / reserved IPs
| Resource |
Status |
Service |
Net EUR / 30d |
| attracs-tms-backend-1 |
RESERVED (unused) |
Compute |
6.70 |
| attracs-tms-backend-2 |
RESERVED (unused) |
Compute |
6.70 |
| tms-backend-5-external |
RESERVED (unused) |
Compute |
6.70 |
| tms-backend-6-external |
RESERVED (unused) |
Compute |
6.71 |
| attracs-services-195009-lb2-address (34.120.155.57) |
IN_USE (LB) |
Compute |
shared |
| attracs-services-195009-lb2n-address (34.96.79.30) |
RESERVED (unused) |
Compute |
shared |
| lb-ipv4-1 (34.120.86.122) |
IN_USE (attracslb) |
Compute |
shared |
1.4 Load balancing (shared across TMS + non-TMS, not split out separately in billing)
- URL map
attracslb routes by host:
- URL map
attracslb-2 default → backend tms-booking
- All backend services point to the same TMS instance groups (
tms-confidential-unmanaged-europe-north1-{a,b,c}-stable, attracs-tms-backends-multi-zone).
1.5 Pub/Sub (split per TMS app via topic naming)
| App |
Net EUR / 30d |
tms-planner (tms.planner.*.trips) |
82.60 |
tms-drivers (tms.drivers.*.vehicles) |
41.60 |
tms-booking (tms.booking.*.bookings) |
17.42 |
tms-notification (cross-app: tms.notification.*) |
1.04 |
| tms-fleet |
0.03 |
| tms-feedback |
0 (no Pub/Sub topics) |
| tms-reports |
0 (no Pub/Sub topics) |
1.6 Storage (TMS-named buckets)
| Resource |
Service |
Net EUR / 30d |
| attracs-tms |
Cloud Storage |
1.59 |
| attracs-tms-dev |
Cloud Storage |
0.00 |
| attracs-tms-feedback |
Cloud Storage |
0.00 |
1.7 attracs-services-195009 — sub-totals by service (TMS-named resources only)
| Service |
Net EUR / 30d |
| Compute Engine |
2,380.73 |
| Confidential Computing |
246.26 |
| Cloud Pub/Sub |
142.77 |
| Cloud Storage |
1.60 |
| Subtotal — TMS-named |
2,771.36 |
1.8 Shared resources in attracs-services-195009 (used by TMS + others — cost not splittable here)
| Resource |
Service |
Net EUR / 30d |
TMS usage |
| attracs-sql |
Cloud SQL (MySQL 5.7, db-n1-standard-4) |
366.43 |
hosts tms_integration_service + superset_* DBs used by tms-reports |
| attracs-spanner-01 |
Cloud Spanner (500 PU, regional europe-north1) |
793.89 |
location* DBs — vehicle/trip location data for tms-drivers / tms-planner |
| attracs-repository |
Artifact Registry (Docker, ~813 GB) |
66.09 |
all attracs Docker images, incl. TMS services |
| Networking (egress / LB / interconnect) |
Networking |
1,011.44 |
TMS share unknown |
1.9 attracs-services-195009 — whole-project cost by service (last 30d, for context)
| Service |
Net EUR / 30d |
| Compute Engine |
3,903.43 |
| Networking |
1,011.44 |
| Cloud Spanner |
793.89 |
| Cloud SQL |
673.31 |
| Confidential Computing |
287.31 |
| Cloud Logging |
271.55 |
| Cloud Monitoring |
246.29 |
| Cloud Pub/Sub |
143.57 |
| Cloud KMS |
76.04 |
| Artifact Registry |
66.09 |
| BigQuery |
45.55 |
| Cloud Storage |
7.42 |
| Directions API |
5.92 |
| Cloud Vision API |
1.91 |
| Network Security |
0.64 |
| Geocoding API |
0.40 |
| Cloud Run Functions |
0.11 |
| Project total |
7,534.88 |
2. PostgreSQL — pg-databases-stable
2.1 Instances
| Resource |
Tier |
Zone |
Status |
Net EUR / 30d |
| pg-1-stable |
db-custom-30-32768 (Postgres 14) |
europe-north1-a |
RUNNABLE |
1,689.78 |
| pg-1-stable-read-replica |
db-custom-4-7680 (Postgres 14) |
europe-north1-c |
RUNNABLE |
196.68 |
| pg-1-stable-master |
Postgres 9.6 (placeholder/legacy entry) |
europe-north1 |
— |
0 |
2.2 Backup storage
| Resource |
Net EUR / 30d |
Automated backups (~70 snapshot rows, a-380684204244-s-…-backup-*) |
137.65 |
| Aggregated other Cloud SQL storage rows |
small |
2.3 TMS databases hosted on pg-1-stable
Cloud SQL bills per instance, not per database, so the figures above are for the whole shared instance. TMS-specific databases on this instance:
| Database |
Likely served app |
| tms_booking |
tms-booking |
| tms_drivers |
tms-drivers |
| tms_feedback |
tms-feedback |
Other databases on the same instance (cost shared with these): contracts, palbassador, ai_booking_assistant, dex, adi_digital_manifest, fleet_manager, cbookings, driver_guides, ao-lime-integration, customer_portal, resource, platoon, scania_connector, logistics_pm, emissions_calculator, road_tower, tailorframe_admin, city_taxi_tower, partner_portal, reporting_engine, road_integrations, greasing_log_stable, road_truck_tower, shiftplanner, daimler_connector (~30 DBs total).
No tms_planner or tms_reports databases here. tms-planner likely uses Spanner (location*) and/or talks to other services; tms-reports likely uses the superset_* MySQL databases in attracs-sql.
2.4 pg-databases-stable — whole-project cost by service (last 30d)
| Service |
Net EUR / 30d |
| Cloud SQL |
2,100.29 |
| Cloud Logging |
2.71 |
| Cloud Monitoring |
2.04 |
| Cloud Storage |
0.06 |
| Project total |
2,100.29 |
3. Optimisation candidates (worth verifying before deleting)
| Item |
Est. monthly savings EUR |
Note |
Delete persistent disks of TERMINATED VMs (tms-backend-5, tms-backend-6, tms-confidential-instance-stable-4, attracs-tms-backends-multi-zone-{2zdb,zq8q}, tms-valhalla-1) |
~97 |
Disks keep billing while VMs are stopped |
Release reserved-but-unused static IPs (attracs-tms-backend-1/2, tms-backend-{5,6}-external, attracs-services-195009-lb2n-address) |
~27 |
RESERVED unused IPs bill ~€2.5–6/month each |
Review reservation sizing — tms-confidential-instance-stable-4 is terminated; if commit covered 4 VMs but only 3 run, ~25% of €1,116 reservation cost is potentially idle |
up to ~280 |
Verify commitment term and target VM count |
Stale Cloud SQL backups in pg-databases-stable (~70 snapshots, oldest from 2023) |
partial of 137.65 |
Tighten retention policy |
4. Caveats
- No per-app labels. The TMS VMs carry
service=tms, customer_environment=tms, deployment_environment=stable — but no app=tms-booking / app=tms-drivers label. The only per-app split available today is via Pub/Sub topic names.
- Cloud SQL / Spanner bill per instance, not per database. The
attracs-sql MySQL and pg-1-stable PostgreSQL each host many non-TMS databases. The figures above show the full instance cost; the TMS share is some unknown fraction.
- Networking €1,011 in
attracs-services-195009 includes egress and LB charges across all apps in the project; no resource-level attribution to TMS without deeper SKU/path analysis.
- Figures are taken from the resource-level billing export. The Cloud Billing console UI may show slightly different totals because it applies presentation-level rounding and credit grouping.
5. Hetzner equivalent — production-grade design and cost comparison
Goal: replicate the TMS workload on Hetzner with production-grade HA. Self-managed clusters for Postgres (replaces Cloud SQL + the Spanner location* DBs) and RabbitMQ (replaces Pub/Sub). No "cheapest tier" — every cluster has at least 3 nodes for quorum, dedicated CPU and ECC RAM where it matters.
5.1 Target architecture
Scope: strictly TMS-attributable resources only. Shared GCP infrastructure (Spanner attracs-spanner-01, attracs-sql MySQL, the project-wide Networking egress, the shared Artifact Registry, the shared observability stack) is excluded on both sides.
- App tier — 3 cloud nodes with dedicated vCPU, running the TMS backends (booking, drivers, feedback, planner, reports, fleet) behind 2 HAProxy/Hetzner LBs, mirroring the current 3-zone GCE layout.
- Postgres cluster — 3 dedicated bare-metal nodes (Patroni + etcd, streaming replication, automatic failover, PgBouncer). Sized to host only the 3 TMS databases (
tms_booking, tms_drivers, tms_feedback) — not the other ~27 databases on pg-1-stable. Much smaller than the GCP shared instance.
- RabbitMQ cluster — 3 cloud nodes with dedicated vCPU (quorum queues / mirrored queues). Replaces the
tms.booking.*, tms.drivers.*, tms.planner.*, tms.notification.* topics.
- Load balancer — 2 Hetzner Cloud Load Balancers in HA, fronting the app tier; TLS terminated at LB.
- etcd — 3 small nodes dedicated to Patroni coordination (don't co-locate with PG).
- Object storage — Hetzner Object Storage (S3-compatible) for the
attracs-tms* buckets.
- Backup tier — pgBackRest to a Storage Box for PITR backups.
5.2 Sizing & costs (Hetzner list prices effective 15 June 2026)
⚠️ Price hike alert. Hetzner adjusted prices on 15 June 2026 (source). Cloud servers went up +100% to +170% (CCX43: €124.99 → €275.99/mo, +121%; CCX23: €31.49 → €85.99/mo, +173%; CCX13: €15.99 → €42.99/mo, +169%). Dedicated AX/EX also rose substantially (AX102 ~€110 → €452.30/mo, ~4×). Existing orders placed before 15 June 2026 keep old prices; new orders and rescales use the new prices, so for a fresh migration today (2026-06-25) the new prices apply. Storage Box, Object Storage and Load Balancers were not listed in the announcement and are assumed unchanged.
The table below uses the post-hike prices. The "old €/mo" column is retained so the magnitude of the change is visible.
| Component |
Replaces on GCP |
Hetzner SKU |
Spec |
Qty |
Old €/mo each |
New €/mo each |
Subtotal new €/mo |
| App tier |
3× tms-confidential-instance-stable-{1,2,3} (n2d-standard-16 confidential VMs) |
CCX43 (dedicated vCPU) |
16 vCPU / 64 GB / 360 GB NVMe |
3 |
124.99 |
275.99 |
827.97 |
| Postgres cluster (Patroni HA) |
TMS share of pg-1-stable (3 of ~30 DBs: tms_booking, tms_drivers, tms_feedback) + replica |
AX42-1 dedicated |
Ryzen 7 7700 8-core / 64 GB DDR5 / 2× 1 TB NVMe (RAID 1) |
3 |
~50 |
187.30 |
561.90 |
| RabbitMQ cluster |
Cloud Pub/Sub TMS topics (tms.booking.*, tms.drivers.*, tms.planner.*, tms.notification.*) |
CCX23 (dedicated vCPU) |
4 vCPU / 16 GB / 160 GB NVMe |
3 |
31.49 |
85.99 |
257.97 |
| etcd (Patroni coordination) |
— (managed inside Cloud SQL) |
CX23 (shared vCPU) |
4 vCPU / 8 GB |
3 |
3.99 |
5.49 |
16.47 |
| Load balancer (HA) |
TMS share of attracslb (paths for 6 TMS backends) |
Hetzner Cloud LB21 |
10k connections, TLS |
2 |
6 |
~6 |
12 |
| Object storage |
attracs-tms, attracs-tms-dev, attracs-tms-feedback GCS buckets |
Hetzner Object Storage |
1 TB included |
1 |
6 |
~6 |
6 |
| Postgres backup target |
TMS share of Cloud SQL backup storage |
Storage Box BX41 |
5 TB |
1 |
13 |
~13 |
13 |
| Bandwidth / egress |
(TMS share excluded — project-wide bill not split) |
Included (20 TB/server/mo) |
— |
— |
0 |
0 |
0 |
| Setup fees (dedicated servers, one-off, amortised over 12 mo) |
— |
AX42-1 setup × 3 |
— |
— |
~10 |
~10 |
~10 |
| Hetzner TOTAL (recurring, TMS-only) |
|
|
|
|
(~470) |
|
~1,705 |
Prices excl. VAT. Verify on hetzner.com/cloud and hetzner.com/dedicated/matrix-ax before committing — Hetzner may adjust again.
5.3 Side-by-side — TMS resources only
| Layer |
GCP resource(s) |
GCP €/30d |
Hetzner equivalent |
Hetzner €/mo |
Δ |
| Compute (3 running TMS VMs + reservations + confidential premium + terminated VM disks) |
tms-confidential-instance-stable-{1,2,3} + zone reservations + Confidential Computing premium + terminated tms-* disks |
2,727 |
3× CCX43 (dedicated 16 vCPU / 64 GB) |
828 |
−1,899 |
PostgreSQL — TMS share of pg-1-stable (3 of ~30 databases, ≈ 10% by count) |
pg-1-stable (€1,690 × 10%) + replica (€197 × 10%) + backups (€138 × 10%) |
~203 |
3× AX42-1 dedicated PG cluster (Patroni HA) |
562 |
+359 |
| Messaging |
TMS Pub/Sub topics (tms.booking.*, tms.drivers.*, tms.planner.*, tms.notification.*) |
143 |
3× CCX23 RabbitMQ cluster |
258 |
+115 |
| Object storage |
attracs-tms, attracs-tms-dev, attracs-tms-feedback GCS buckets |
2 |
Hetzner Object Storage 1 TB |
6 |
+4 |
| Load balancer + IPs |
TMS share of attracslb / attracslb-2 + reserved IPs |
0 (LB cost not split out) |
2× LB21 |
12 |
+12 |
| Backup storage |
(included in PG row above) |
— |
Storage Box BX41 5 TB |
13 |
+13 |
| etcd coordination |
— (managed inside Cloud SQL) |
0 |
3× CX23 |
16 |
+16 |
| Setup fees (amortised) |
— |
0 |
AX42 setup × 3 |
10 |
+10 |
| Total — TMS-only |
|
~3,075 |
|
~1,705 |
−1,370 (~45%) |
Scope note: this side-by-side excludes Spanner attracs-spanner-01 (€794/30d), attracs-sql MySQL, project-wide Networking egress (€1,011/30d), the shared Artifact Registry, and the shared Cloud Logging/Monitoring stack. Those are not strictly TMS-attributable — they serve the whole project. If you later want to retire them too, the Hetzner side would need a Spanner replacement (e.g. CockroachDB cluster, ~+€600–1,000/mo at post-hike pricing), a registry node, and an observability node.
Sensitivity to the 15 June 2026 hike:
Pre-hike, the same Hetzner stack would have cost ~€470/mo → saving was ~€2,600/mo (~85%).
Post-hike (today's prices), the saving is ~€1,370/mo (~45%).
Add 0.5–1 FTE for self-hosted ops (~€3.3–10k/mo loaded cost) and the cost saving disappears entirely. The remaining case for Hetzner is then vendor independence, predictable pricing, zero egress fees, not raw EUR.
5.4 What we lose by moving to Hetzner
- Confidential VMs (AMD SEV-SNP). Hetzner doesn't offer confidential computing today. If the
tms-confidential-instance-stable-* naming reflects an actual compliance requirement (encrypted RAM in use), this is a blocker. If it's "nice-to-have", it's fine to drop.
- Cross-region SQL strong consistency. Spanner gives globally-consistent SQL with TrueTime. Postgres + Patroni gives single-region HA only. If the
location* data needs cross-region writes, CockroachDB self-hosted is the closer match (and would cost more — roughly +3× the PG cluster).
- Managed backups, PITR, automatic failover for SQL. All self-operated. Plan for pgBackRest + Patroni + monitoring + on-call.
- Pub/Sub guarantees. GCP Pub/Sub gives at-least-once delivery with multi-region durability out of the box; RabbitMQ quorum queues are good but need careful network and disk sizing to match.
- GCP IAM, audit logs, Cloud Logging retention. Need to replicate with Authelia/Keycloak + centralised syslog + Loki retention rules.
- People cost. Self-hosting these clusters is at least 0.5–1 FTE of SRE time. At a €80–120k loaded cost, that's another €3.3–10k/mo — so the apples-to-apples saving is closer to €2–4k/mo once you internalise ops.
5.5 Reasonable next step
Don't migrate everything at once. Lowest-risk wave:
- RabbitMQ first (smallest blast radius, clean abstraction). Validate the messaging pattern off GCP, get ops practice. Saves ~€50/mo immediately but more importantly de-risks the next steps.
- Stateless app tier next. Containerise on K3s or Nomad on the 3 CCX43 nodes. Cut LB DNS over. Saves ~€2k/mo.
- Postgres last. This is where the operational risk concentrates. Run Hetzner PG in parallel with
pg-1-stable using logical replication for ≥1 month before cutover.
- Spanner — only attempt to replace once you've measured the
location* traffic profile. If write throughput is high or geo-distribution matters, leave it on GCP and budget for it.