Skip to content

Instantly share code, notes, and snippets.

@Illyism
Last active December 15, 2025 17:32
Show Gist options
  • Select an option

  • Save Illyism/2a00e958d26b70c870cafc0d9d703d1d to your computer and use it in GitHub Desktop.

Select an option

Save Illyism/2a00e958d26b70c870cafc0d9d703d1d to your computer and use it in GitHub Desktop.
Cost Comparison: PlanetScale Metal Postgres vs Hetzner EX63
#!/usr/bin/env bun
/**
* Cost Comparison: PlanetScale Metal Postgres vs Hetzner EX63
*
* Uses actual PlanetScale pricing data (Dec 2025)
* Reference region: us-east-1 (N. Virginia) - AWS
*
* 👉 HETZNER PROMO CODE: https://il.ly/go/hetzner
*/
interface PlanetScaleTier {
name: string
ram: number // GB
cpu: number // vCPU
storage: number // GB
price: number // USD/month (us-east-1, x86-64)
architecture: "x86-64" | "aarch64"
type: "PS" | "M-METAL"
}
interface HetznerServer {
name: string
cpu: string
ram: number // GB
storage: number // GB (total)
basePrice: number // EUR/month
setupFee: number // EUR one-time
}
interface CostBreakdown {
monthly: number
annual: number
threeYear: number
setup?: number
}
// Actual PlanetScale pricing (us-east-1, x86-64) from pricing data
// PS tiers (EBS storage)
const planetScalePSTiers: PlanetScaleTier[] = [
{
name: "PS-DEV",
ram: 0.125,
cpu: 0.125,
storage: 0,
price: 10,
architecture: "x86-64",
type: "PS",
},
{ name: "PS-10", ram: 1, cpu: 0.125, storage: 0, price: 39, architecture: "x86-64", type: "PS" },
{ name: "PS-20", ram: 2, cpu: 0.25, storage: 0, price: 59, architecture: "x86-64", type: "PS" },
{ name: "PS-40", ram: 4, cpu: 0.5, storage: 0, price: 99, architecture: "x86-64", type: "PS" },
{ name: "PS-80", ram: 8, cpu: 1, storage: 0, price: 179, architecture: "x86-64", type: "PS" },
{ name: "PS-160", ram: 16, cpu: 2, storage: 0, price: 349, architecture: "x86-64", type: "PS" },
{ name: "PS-320", ram: 32, cpu: 4, storage: 0, price: 699, architecture: "x86-64", type: "PS" },
{ name: "PS-640", ram: 64, cpu: 8, storage: 0, price: 1399, architecture: "x86-64", type: "PS" },
]
// Metal tiers (us-east-1, x86-64) - matching Hetzner's 64GB RAM
const planetScaleMetalTiers: PlanetScaleTier[] = [
{
name: "M-160 D-METAL-118",
ram: 16,
cpu: 2,
storage: 118,
price: 609,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-160 D-METAL-468",
ram: 16,
cpu: 2,
storage: 468,
price: 729,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-160 D-METAL-1250",
ram: 16,
cpu: 2,
storage: 1250,
price: 1009,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-320 D-METAL-237",
ram: 32,
cpu: 4,
storage: 237,
price: 1179,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-320 D-METAL-937",
ram: 32,
cpu: 4,
storage: 937,
price: 1429,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-320 D-METAL-2500",
ram: 32,
cpu: 4,
storage: 2500,
price: 1999,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-640 D-METAL-474",
ram: 64,
cpu: 8,
storage: 474,
price: 2329,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-640 D-METAL-1875",
ram: 64,
cpu: 8,
storage: 1875,
price: 2839,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-640 D-METAL-5000",
ram: 64,
cpu: 8,
storage: 5000,
price: 3959,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-1280 D-METAL-950",
ram: 128,
cpu: 16,
storage: 950,
price: 4629,
architecture: "x86-64",
type: "M-METAL",
},
{
name: "M-1280 D-METAL-3750",
ram: 128,
cpu: 16,
storage: 3750,
price: 5639,
architecture: "x86-64",
type: "M-METAL",
},
]
// Storage and egress pricing (per GB/month)
const storagePricing: Record<string, number> = {
"us-east-1": 0.125, // $0.125 per GB/month
"us-east-2": 0.125,
"us-west-2": 0.125,
"eu-central-1": 0.149,
"eu-west-1": 0.138,
"eu-west-2": 0.145,
}
const egressPricing: Record<string, number> = {
"us-east-1": 0.06, // $0.06 per GB egress
"us-east-2": 0.06,
"us-west-2": 0.06,
"eu-central-1": 0.06,
"eu-west-1": 0.06,
"eu-west-2": 0.06,
}
// Hetzner EX63 pricing
const hetznerEX63: HetznerServer = {
name: "EX63",
cpu: "Intel Core Ultra 7 265 (20 cores: 12E + 8P)",
ram: 64, // GB
storage: 2000, // GB (2x 1TB NVMe SSD RAID 1)
basePrice: 66, // EUR/month
setupFee: 39, // EUR one-time
}
function calculateHetznerCost(server: HetznerServer): CostBreakdown {
// Convert EUR to USD (approximate rate: 1 EUR = 1.08 USD)
const eurToUsd = 1.08
const monthlyUsd = server.basePrice * eurToUsd
const setupUsd = server.setupFee * eurToUsd
return {
monthly: monthlyUsd,
annual: monthlyUsd * 12,
threeYear: monthlyUsd * 36 + setupUsd, // Include setup fee once
setup: setupUsd,
}
}
function findBestPlanetScaleTier(
ramGB: number,
storageGB: number,
): { tier: PlanetScaleTier; cost: CostBreakdown } | null {
// First try Metal tiers (better performance)
const metalTiers = planetScaleMetalTiers.filter(t => t.ram >= ramGB && t.storage >= storageGB)
if (metalTiers.length > 0) {
// Use the smallest suitable Metal tier
const tier = metalTiers[0]
return {
tier,
cost: {
monthly: tier.price,
annual: tier.price * 12,
threeYear: tier.price * 36,
},
}
}
// Fallback to PS tiers
const psTiers = planetScalePSTiers.filter(t => t.ram >= ramGB)
if (psTiers.length > 0) {
const tier = psTiers[0]
// PS tiers have separate storage pricing - estimate storage cost
const region = "us-east-1"
const storageCost = storageGB * storagePricing[region]
const monthly = tier.price + storageCost
return {
tier,
cost: {
monthly,
annual: monthly * 12,
threeYear: monthly * 36,
},
}
}
return null
}
function formatCurrency(amount: number, currency = "USD"): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount)
}
function formatBytes(bytes: number): string {
if (bytes >= 1000) {
return `${(bytes / 1000).toFixed(1)} TB`
}
return `${bytes} GB`
}
console.log("=".repeat(80))
console.log("DATABASE COST COMPARISON: PlanetScale Metal vs Hetzner EX63")
console.log("Using actual PlanetScale pricing (us-east-1, x86-64)")
console.log("=".repeat(80))
console.log()
// Show Hetzner costs
const hetznerCost = calculateHetznerCost(hetznerEX63)
console.log("HETZNER EX63 (Dedicated Server)")
console.log("-".repeat(80))
console.log(`CPU: ${hetznerEX63.cpu}`)
console.log(`RAM: ${hetznerEX63.ram} GB`)
console.log(`Storage: ${formatBytes(hetznerEX63.storage)} (2x 1TB NVMe RAID 1)`)
console.log(
`Monthly: ${formatCurrency(hetznerEX63.basePrice, "EUR")} (${formatCurrency(hetznerCost.monthly)})`,
)
if (hetznerCost.setup) {
console.log(
`Setup Fee: ${formatCurrency(hetznerEX63.setupFee, "EUR")} (${formatCurrency(hetznerCost.setup)})`,
)
}
console.log(`Annual: ${formatCurrency(hetznerCost.annual)}`)
console.log(`3-Year Total: ${formatCurrency(hetznerCost.threeYear)}`)
console.log()
// Direct comparison: Hetzner vs PlanetScale M-640 (matching 64GB RAM)
console.log("=".repeat(80))
console.log("DIRECT COMPARISON: 64GB RAM, 2TB Storage")
console.log("=".repeat(80))
console.log()
const hetznerSpec = { ram: 64, storage: 2000 }
const planetScaleMatch = findBestPlanetScaleTier(hetznerSpec.ram, hetznerSpec.storage)
if (planetScaleMatch) {
console.log(`PlanetScale ${planetScaleMatch.tier.name}:`)
console.log(` RAM: ${planetScaleMatch.tier.ram} GB`)
console.log(` CPU: ${planetScaleMatch.tier.cpu} vCPU`)
console.log(` Storage: ${formatBytes(planetScaleMatch.tier.storage)}`)
console.log(` Monthly: ${formatCurrency(planetScaleMatch.cost.monthly)}`)
console.log(` Annual: ${formatCurrency(planetScaleMatch.cost.annual)}`)
console.log(` 3-Year: ${formatCurrency(planetScaleMatch.cost.threeYear)}`)
console.log()
const diff = {
monthly: hetznerCost.monthly - planetScaleMatch.cost.monthly,
annual: hetznerCost.annual - planetScaleMatch.cost.annual,
threeYear: hetznerCost.threeYear - planetScaleMatch.cost.threeYear,
}
console.log("Cost Difference (Hetzner - PlanetScale):")
console.log(
` Monthly: ${formatCurrency(diff.monthly)} ${diff.monthly > 0 ? "(Hetzner more expensive)" : "(Hetzner cheaper)"}`,
)
console.log(` Annual: ${formatCurrency(diff.annual)}`)
console.log(` 3-Year: ${formatCurrency(diff.threeYear)}`)
console.log()
// Calculate savings percentage
const savingsPercent =
((planetScaleMatch.cost.monthly - hetznerCost.monthly) / planetScaleMatch.cost.monthly) * 100
console.log(`Hetzner saves ${Math.abs(savingsPercent).toFixed(1)}% compared to PlanetScale`)
console.log()
}
// Compare different storage options for M-640
console.log("=".repeat(80))
console.log("PLANETSCALE M-640 TIERS (64GB RAM) - Storage Options")
console.log("=".repeat(80))
console.log()
const m640Tiers = planetScaleMetalTiers.filter(t => t.ram === 64)
for (const tier of m640Tiers) {
const diff = hetznerCost.monthly - tier.price
console.log(`${tier.name}:`)
console.log(` Storage: ${formatBytes(tier.storage)}`)
console.log(` Price: ${formatCurrency(tier.price)}/month`)
console.log(
` vs Hetzner: ${formatCurrency(diff)}/month ${diff > 0 ? "(Hetzner more)" : "(Hetzner saves " + formatCurrency(Math.abs(diff)) + ")"}`,
)
console.log()
}
// Resource efficiency comparison
console.log("=".repeat(80))
console.log("RESOURCE EFFICIENCY COMPARISON")
console.log("=".repeat(80))
console.log()
const hetznerRamPerDollar = hetznerEX63.ram / hetznerCost.monthly
const hetznerStoragePerDollar = hetznerEX63.storage / hetznerCost.monthly
if (planetScaleMatch) {
const psRamPerDollar = planetScaleMatch.tier.ram / planetScaleMatch.cost.monthly
const psStoragePerDollar = planetScaleMatch.tier.storage / planetScaleMatch.cost.monthly
console.log("RAM Efficiency:")
console.log(` Hetzner: ${hetznerRamPerDollar.toFixed(2)} GB RAM per $1/month`)
console.log(` PlanetScale: ${psRamPerDollar.toFixed(2)} GB RAM per $1/month`)
console.log(
` Hetzner provides ${(hetznerRamPerDollar / psRamPerDollar).toFixed(1)}x more RAM per dollar`,
)
console.log()
console.log("Storage Efficiency:")
console.log(` Hetzner: ${hetznerStoragePerDollar.toFixed(2)} GB storage per $1/month`)
console.log(` PlanetScale: ${psStoragePerDollar.toFixed(2)} GB storage per $1/month`)
console.log(
` Hetzner provides ${(hetznerStoragePerDollar / psStoragePerDollar).toFixed(1)}x more storage per dollar`,
)
console.log()
}
// Show smaller tiers for reference
console.log("=".repeat(80))
console.log("PLANETSCALE SMALLER TIERS (for reference)")
console.log("=".repeat(80))
console.log()
const smallTiers = [
...planetScalePSTiers.filter(t => t.ram <= 8),
...planetScaleMetalTiers.filter(t => t.ram <= 32),
].sort((a, b) => a.price - b.price)
for (const tier of smallTiers.slice(0, 10)) {
console.log(
`${tier.name}: ${tier.ram}GB RAM, ${tier.cpu} vCPU, ${formatBytes(tier.storage)} storage - ${formatCurrency(tier.price)}/month`,
)
}
console.log()
// Summary
console.log("=".repeat(80))
console.log("SUMMARY")
console.log("=".repeat(80))
console.log()
if (planetScaleMatch) {
const monthlyDiff = hetznerCost.monthly - planetScaleMatch.cost.monthly
const annualDiff = hetznerCost.annual - planetScaleMatch.cost.annual
const threeYearDiff = hetznerCost.threeYear - planetScaleMatch.cost.threeYear
console.log("For 64GB RAM, 2TB storage:")
console.log(` PlanetScale: ${formatCurrency(planetScaleMatch.cost.monthly)}/month`)
console.log(` Hetzner: ${formatCurrency(hetznerCost.monthly)}/month`)
console.log(
` Difference: ${formatCurrency(monthlyDiff)}/month (${monthlyDiff > 0 ? "Hetzner costs more" : "Hetzner saves " + formatCurrency(Math.abs(monthlyDiff))})`,
)
console.log()
console.log(`Annual difference: ${formatCurrency(annualDiff)}`)
console.log(`3-Year difference: ${formatCurrency(threeYearDiff)}`)
console.log()
}
console.log("Key Considerations:")
console.log(" 1. Hetzner: 64GB RAM, 2TB storage, 20 cores for $71/month")
console.log(" 2. PlanetScale M-640: 64GB RAM, 474GB-5TB storage, 8 vCPU for $2,329-$3,959/month")
console.log(" 3. Hetzner provides 32x more storage capacity at 1/33rd the cost")
console.log(" 4. PlanetScale is fully managed (backups, updates, monitoring included)")
console.log(" 5. Hetzner requires self-management but gives full server control")
console.log(" 6. PlanetScale offers online scaling; Hetzner requires manual migration")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment