← Writing
#18··16 min read

How I Stopped Paying £38/Month Per Database (and Got Microsecond Response Times)

Why I'm starting new indie projects on a €6/month Hetzner VPS instead of managed platforms—and why you might too

The £38 Waitlist

I had a great idea. Like most great ideas from indie hackers, it needed validation before building anything serious. Step one: put up a waitlist, see if anyone cares.

Step two: realise that Fly.io's managed Postgres costs £38 per month per database instance.

For a waitlist.

Not a production database serving thousands of users. Not a revenue-generating SaaS product. A waitlist. A single table. Email, name, timestamp. Maybe a few hundred rows if I'm lucky.

£38/month for a waitlist is £38/month you didn't validate your idea.

Here's the thing: I experiment a lot. I build things, test ideas, see what sticks. That's what indie hackers do. Most projects fail. A few get traction. Maybe one succeeds.

At £38 per database, ten projects is £380/month. Just for Postgres. Before apps, before bandwidth, before anything actually runs.

I needed a different approach. Not because Fly.io is bad—it's not, it's great—but because the economics of managed Postgres don't align with the economics of indie experimentation.

So I moved my new projects to a Hetzner VPS running Dokploy.


The Reframe: Platform Incentives vs Indie Economics

This isn't about Fly.io being evil or greedy. They're not. Managed Postgres at £38/month is reasonable for production workloads. For enterprises, it's a rounding error.

The Real Problem

The issue is incentive misalignment.

Managed platforms optimise for:

  • Predictable revenue (monthly recurring per service)
  • Enterprise customers (who value convenience over cost)
  • Vertical scaling (more resources = higher tier = more money)

Indie hackers optimise for:

  • Cheap experimentation (validate before spending)
  • High project velocity (10 experiments, 1 success)
  • Horizontal scaling (more projects on same infrastructure)

These aren't compatible economics.

Fly.io used to be the cheap, easy option for Elixir projects. Excellent integration, fast deploys, generous free tier. I ran several projects there happily at ~£50/month total.

Then they deprecated shared Postgres and made managed instances the only option. Suddenly, the calculus changed. Not for production apps—those are still worth paying for. But for experiments? For waitlists? For side projects that might never make a dollar?

The math stopped working.

I tried Neon (free tier Postgres). It's been flakey in my experience. For anything that matters, I don't trust it. So that's out.

It's like streaming services. Remember when Netflix was £8/month and you had everything? Now you're paying £80 across six platforms because every studio wants their own subscription.

This is the SaaS sprawl problem in microcosm. Every service is rational individually. Collectively, they're insane. You start with one Heroku app (easy!), add Heroku Postgres (convenient!), then another app, another database, maybe a Redis instance, and suddenly you're paying £200/month for side projects that generate £0.

The platforms want you to scale vertically (bigger instance = more money). You need to scale horizontally (more projects = same cost).


The Economics: Before and After

Let me show you the math.

Before (Fly.io with managed Postgres):

  • Running costs: ~£50/month for apps + old shared Postgres
  • Then: forced migration to managed Postgres
  • New pricing: £38/month per database instance
  • My project velocity: easily 10+ experiments per year
  • Projected cost: £400+/month just for databases

After (Hetzner CX33 + Dokploy):

  • €5.99/month (~£5/month at current rates)
  • Specs: 4 vCPU, 8GB RAM, 80GB NVMe SSD
  • Currently running:
    • defer.to (Elixir app)
    • Postgres (one instance, multiple databases)
    • Umami (self-hosted analytics)
    • Uptime Kuma (self-hosted monitoring)
  • Current RAM usage: 1.5GB (plenty of headroom)
  • Room to grow: 10+ projects easily

Let's be clear about this:

One Fly.io managed Postgres instance costs 7-8x more than an entire Hetzner VPS running multiple apps, databases, analytics, and monitoring.

That's not a pricing complaint. That's a recognition that the economics have changed. Fly.io is optimising for a different customer than me right now.

To be clear: I'm not moving everything off Fly.io. I'm not anti-Fly. For production apps that need scale, management, and reliability, managed platforms make total sense. Pay for convenience when you're making money.

But for new experiments? For validation? For side projects that might never generate revenue?

I'm starting on Hetzner.

The unexpected bonuses:

  • Deploy speed: Docker builds complete in under 10 seconds (Fly took minutes)
  • Performance: Microsecond response times on some requests (the CX33 is beefy compared to Fly's tiny instances)
  • Simplicity: Everything in one place, one bill, one mental model
Start cheap, graduate to managed when you're making money.

The Model: Hetzner + Dokploy + One Postgres Instance

Here's what this setup actually looks like:

Hetzner Cloud: European VPS provider with incredible price/performance. The CX33 at €5.99/month is absurdly cheap for what you get.

Dokploy: Open-source PaaS (think Heroku or Fly, but you host it). Docker-based, web UI for deployments, built-in Postgres management, GitHub integration, automatic SSL. It's the abstraction layer that makes this feel like a managed platform.

The Architecture:

Hetzner VPS (CX33)
├── Dokploy (orchestration + UI)
├── Postgres (one instance, multiple databases)
├── App containers (defer.to, waitlist app, future projects)
└── Self-hosted tools (Umami, Uptime Kuma)

The One-Postgres-Many-Databases Pattern

Key Pattern

You don't need multiple Postgres instances. You need multiple databases within one instance.

Here's how it works:

  • Run one Postgres instance on the VPS
  • Create separate databases for each app: defer_to_db, waitlist_db, project_xyz_db
  • Each app gets its own credentials, its own schema, total isolation
  • No cross-contamination, but shared infrastructure

In SQL:

CREATE DATABASE waitlist_db;
CREATE USER waitlist_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE waitlist_db TO waitlist_user;

Each app points to its own database URL, completely unaware that other databases exist.

Why this works for indie hackers:

  • Fixed cost: One Postgres instance = €5.99/month total, regardless of how many databases
  • Cheap experimentation: Spin up new apps, create new databases, no new bills
  • Consolidation: One backup strategy, one monitoring target, one thing to manage
  • Ownership: No surprise pricing changes, you control the entire stack

One Postgres instance is a fixed cost. Ten managed databases is a variable nightmare.


How to Do It: The Step-by-Step Guide

Fair warning: my Linux-fu is rusty. I last did serious server work in 2015. So I had Gemini open the entire time, asking things like "is this the right command?" and "what does this flag do?"

This is actually a feature, not a bug. Setting this up forced me to practice fundamental Linux skills again, which is invaluable if you develop on a Mac.

Step 1: Provision the Hetzner VPS

  1. Sign up at Hetzner Cloud
  2. Create a new project
  3. Choose a region (Nuremberg, Helsinki, Falkenstein—pick one close to your users)
  4. Select CX33 (4 vCPU, 8GB RAM, 80GB NVMe, €5.99/month)
  5. Add your SSH key (generate one if you don't have it: ssh-keygen -t ed25519)
  6. Launch the server

You'll get an IP address. Save it.

Step 2: Install Dokploy

SSH into your VPS:

ssh root@YOUR_VPS_IP

Run Dokploy's one-line installer:

curl -sSL https://dokploy.com/install.sh | sh

Wait a few minutes. Dokploy will:

  • Install Docker
  • Set up the web UI
  • Configure networking
  • Start the management interface

Access the UI at http://YOUR_VPS_IP:3000. Create an admin account.

Step 3: Secure SSH with Tailscale

Public SSH is a security risk. Use Tailscale to create a private network between your machines and the VPS.

On the VPS:

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up

On your local machine: Install Tailscale (Mac, Windows, Linux all supported), launch it, sign in to the same account.

Now both machines are on your Tailnet (private network). The VPS gets a Tailscale IP like 100.x.x.x.

SSH via Tailscale:

ssh root@100.x.x.x

Your VPS is now only accessible via Tailscale, not the public internet. Much safer.

Step 4: Set Up Postgres in Dokploy

In the Dokploy web UI:

  1. Go to DatabasesCreate Postgres
  2. Name it (e.g., main-postgres)
  3. Set a strong root password
  4. Deploy

Dokploy spins up a Postgres container. Note the connection details.

For each app, create a new database:

SSH into the VPS, connect to Postgres:

docker exec -it <postgres-container-name> psql -U postgres

Then:

CREATE DATABASE app_name_db;
CREATE USER app_name_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE app_name_db TO app_name_user;
\q

Each app gets its own database and credentials. Total isolation, shared infrastructure.

Bonus: Local Database Access via Tailscale

Since you're on Tailscale, you can connect to the Postgres instance directly from your local machine using any database client.

I use Beekeeper Studio (free, cross-platform, excellent UI). Just point it to:

  • Host: 100.x.x.x (your VPS's Tailscale IP)
  • Port: 5432
  • Database: app_name_db
  • User: app_name_user
  • Password: [your password]

Now you can browse tables, run queries, and debug locally without SSHing into the VPS. Tailscale makes this secure—the connection is encrypted and only accessible via your private network.

Step 5: Deploy Your First App

I'm using Elixir with mix releases for defer.to, which makes Docker deployment trivial.

The Dockerfile approach:

  • Multi-stage build: Builder stage (compile Elixir, run mix release) + Runner stage (minimal Alpine image, copy release binary)
  • Result: Small, production-ready container
  • Build time: under 10 seconds (compared to Fly's multi-minute deploys)

In Dokploy:

  1. Create a new Application
  2. Connect your GitHub repo
  3. Point to the Dockerfile in your repo
  4. Set environment variables:
    • DATABASE_URL=postgresql://app_user:password@postgres:5432/app_db
    • SECRET_KEY_BASE=...
    • Any other config
  5. Click Deploy

Dokploy:

  • Pulls the repo
  • Builds the Docker image (watch this happen in under 10 seconds—it's wild)
  • Starts the container
  • Maps it to a port

Coming from Fly's multi-minute deploys, watching this complete in 10 seconds feels like magic. And because it's just Docker, there's no platform-specific config, no buildpacks, no magic. Just a Dockerfile.

Step 6: Configure Domains and SSL

In Dokploy, under your app settings:

  1. Add your domain (e.g., defer.to)
  2. Point your DNS A record to the Hetzner VPS IP
  3. Dokploy automatically provisions a Let's Encrypt SSL certificate

Wait a few minutes for DNS propagation. That's it. No certificate gymnastics, no manual renewal setup. It just works.

Step 7: Set Up S3 Backups

Critical: Database Backups

With managed Postgres, backups are automatic. With self-hosted, you must set them up.

A backup you haven't tested restoring is Schrödinger's backup.

  1. Create an S3 bucket (AWS S3, Backblaze B2, Cloudflare R2—pick one)
  2. In Dokploy, go to Postgres settingsBackups
  3. Configure:
    • S3 endpoint
    • Bucket name
    • Access key & secret
    • Backup schedule (daily recommended)
  4. Test a restore immediately

Dokploy handles the backup automation. You just need to verify it works.

Step 8: Self-Host the Good Stuff

Now that you have a VPS, you can self-host tools you'd otherwise pay for:

Umami (web analytics):

  • Open-source, privacy-focused analytics
  • One-click deploy in Dokploy (it's just a Docker container)
  • Point it to a database in your Postgres instance
  • No more paying for analytics SaaS, no data sharing with Google

Uptime Kuma (monitoring):

  • Self-hosted uptime monitoring (like Pingdom or UptimeRobot)
  • Monitors your apps, sends alerts if things go down
  • Deploy via Dokploy

All of this—defer.to, Umami, Uptime Kuma, Postgres—uses 1.5GB of RAM. You've got 8GB. Plenty of room to grow.

Self-hosting Umami means no analytics bills, no data sharing, just clean metrics you control.

The Unexpected Benefits

I expected to save money. I didn't expect these bonuses:

You Learn Linux Again (or for the First Time)

If you develop on a Mac (I do), you interact with the terminal, but you don't really run servers. SSHing into a VPS, tailing logs, checking disk usage, debugging network issues—this is invaluable practice.

I had Gemini open throughout the setup, asking things like:

  • "What's the command to check disk usage?"
  • "How do I restart a Docker container?"
  • "Is this the right way to configure DNS?"

This is a feature of modern sysadmin work. You don't need to memorize every command. You need to know what to ask, and how to verify the answer. Using an AI assistant to double-check your Linux-fu is smart, not cheating.

Side benefit: your skills sharpen. You become a better developer. You understand the stack more deeply.

The Learning Dividend

Every deploy is a chance to practice fundamental skills. Every configuration tweak is a learning opportunity.

If you're rusty on Linux (like I was), this is a forcing function to get better.

Deployment Becomes Shockingly Simple

Dockerfile-based deploys are wonderfully simple:

  • No platform-specific config
  • No buildpacks, no magic
  • Just: "Here's a Dockerfile, run it"

And they're fast. Under 10 second builds. Coming from Fly's multi-minute deploys, this changes how you work. You deploy more frequently because it's not a context switch.

Fast deploys aren't just efficient—they change your workflow.

Performance Is Noticeably Better

The Hetzner CX33 is a beefy machine compared to Fly's tiny shared instances. Response times that were "fast" on Fly are now "microsecond-range" on Hetzner.

Is this overkill for a waitlist? Absolutely. But it's nice knowing you have headroom.

Mental Model Simplification

This one surprised me: more control = less complexity.

Before:

  • Fly dashboard for apps
  • (Would have been) Multiple Postgres instances to manage
  • Separate analytics service (if not self-hosting Umami)
  • Separate monitoring service (if not self-hosting Uptime Kuma)

After:

  • One VPS
  • One Dokploy UI
  • One place to check logs, metrics, everything

Consolidation simplifies the mental model. Instead of juggling multiple dashboards and accounts, it's all in one place.


Gotchas & When This Doesn't Make Sense

This approach isn't perfect. Here's what to watch for:

You're Responsible for Uptime

Managed services = someone else's pager. Self-hosted = you're the pager.

Hetzner is reliable, but if the VPS goes down, your apps go down. There's no automatic failover, no multi-region redundancy.

Mitigation:

  • Use Uptime Kuma to monitor and alert you
  • Start with non-critical projects
  • Keep revenue-generating apps on managed platforms until you're confident

You Need Basic Sysadmin Skills

You don't need to be a Linux wizard, but you do need to be comfortable:

  • SSHing into a server
  • Reading logs
  • Updating packages occasionally
  • Debugging when things break

Mitigation:

  • Use Dokploy's abstractions (it handles most Docker complexity)
  • Lean on AI assistants (Gemini, Claude, ChatGPT) to check your work
  • Start small, learn as you go

Not Suitable for High-Traffic Apps (Yet)

One VPS = one point of failure. No horizontal scaling, no load balancing across regions.

For validated, revenue-generating products with real traffic, managed platforms make sense.

The sweet spot: Experiments, side projects, early-stage validation, waitlists, internal tools.

Database Backups Are Critical

Test Your Backups

With managed Postgres, backups are automatic and tested by the provider.

With self-hosted, you must set up S3 backups and actually test restores.

A backup you haven't restored is Schrödinger's backup. It might work. It might not. You won't know until you need it.

Set up backups on day one. Test a restore within the first week.

Time Investment

  • Initial setup: A few hours (mostly learning Dokploy, configuring SSH, setting up Tailscale)
  • Ongoing maintenance: Minimal, but non-zero (package updates, log checks, monitoring)

Counter-argument: I spent hours before juggling dashboards, comparing pricing tiers, and calculating costs. This is just a different time investment.

When Managed Services DO Make Sense

Use managed platforms like Fly.io, Render, Railway when:

  • You're making money (revenue justifies convenience)
  • High traffic (need auto-scaling, load balancing, multi-region)
  • Team environment (multiple devs, need collaboration features)
  • Compliance requirements (SOC2, HIPAA, etc.)
  • Your time is more valuable than the cost (bootstrapped and profitable)

This isn't about managed vs self-hosted being "better." It's about matching infrastructure economics to your current stage.

Validate on a VPS. Scale on managed platforms.

The Broader Point: Experimentation Economics

Here's what this really taught me:

Default Platform Choices Compound

You start with Heroku (easy!), add Heroku Postgres (convenient!), maybe Redis (just $10/month!), then another app, another database...

Each decision is rational individually. Collectively, they're insane.

The SaaS sprawl problem: $10 here, $20 there, suddenly $200/month for side projects that generate $0.

This is the tax on defaults. Platforms want you to take the easy path because it's profitable for them.

Ownership Has Different Economics

  • Renting scales linearly: 10 apps = 10x cost
  • Owning scales sub-linearly: 10 apps on one VPS = same cost

For indie hackers with high project velocity (lots of experiments, few winners), ownership wins.

The Validation Paradox

You need cheap infrastructure to validate ideas quickly. But modern platforms want you to pay before you know if it'll work.

£38/month for a waitlist database is the perfect example. You're paying production prices for pre-validation experiments.

The best validation tool is the one you don't pay for monthly.

Skills Compound Too

Running your own VPS forces you to understand the stack. Every deploy is a learning opportunity. Every configuration tweak deepens your knowledge.

Managed platforms abstract away complexity, which is great when you're making money. But when you're learning and experimenting, the complexity is valuable.

Rusty Linux-fu + AI assistant = modern sysadmin onboarding. You don't need to memorize everything. You need to know what's possible and how to verify it.

(I still Google "how to check disk usage" every time. It's df -h, and I'll probably forget again.)

The Indie Hacker Stack Should Optimise for Experimentation

Not enterprise scale. Not team collaboration. Not compliance.

Optimise for:

  • Cheap validation (ideas are free, infrastructure shouldn't be expensive)
  • High project velocity (10 experiments = 10 learnings, even if 9 fail)
  • Skill building (the things you learn compound over time)
  • Consolidation, not sprawl (one mental model, not ten dashboards)

A final thought:

Look at your current hosting bills. Add them up. Now ask: Could one powerful VPS run all of this?

If the answer is yes, you're paying for convenience you might not need yet.

If the answer is no (high traffic, team needs, compliance), you're probably paying for the right things.

The goal isn't to self-host everything forever. It's to match your infrastructure costs to your current stage.

Start cheap. Experiment freely. Graduate to managed services when you're making money.

Your infrastructure should scale with your revenue, not your ambition.


I hope you found this post useful. Subscribe to my Substack below for similar content and follow me on Twitter and Bluesky for more indie hacker tips and Elixir/programming content.

Check out defer.to—the Elixir app I'm hosting on this very setup!