Self-Hosting on a Budget: Run Real Apps for $10/mo

by David Park

Most people assume self-hosting means either a $5 DigitalOcean droplet that falls over under load, or a $200/month dedicated box that only enterprises can justify. Both assumptions are wrong.

Self-hosting on a budget is genuinely viable for indie hackers running real production workloads — SaaS apps, internal tools, side projects with paying customers. I've been doing it for nine years, and right now my entire stack costs $40/month on Hetzner. Some of my projects run on a single $4.51/month CAX11 ARM instance and handle thousands of requests per day without breaking a sweat.

This post covers the actual decisions that keep your bill low without sacrificing reliability: which providers to use, how to size your server correctly, what to run (and what not to run) on a single VPS, and a few configuration tricks that matter in practice.

Why Most Budget Hosting Advice Is Useless

The standard advice is "just use a $5 VPS." That's not advice — that's a price point. It tells you nothing about whether that instance will survive a traffic spike, how you handle backups when the disk is 25 GB, or what happens when you need to run Postgres, Redis, and a Node app on the same box.

Vendor marketing makes this worse. Every cloud provider has a "free tier" designed to get you hooked, then gradually push you toward $50–$100/month plans once your project grows. AWS Lightsail, Railway, Render — they all do this. The free tier is a loss leader. Self-hosting on a budget means opting out of that escalation entirely.

The honest framework: pick a provider with transparent pricing, right-size your instance from the start, and automate the boring ops work so you're not paying with time instead of money.

Choosing the Right Provider

I'll be direct: Hetzner is the best value for most indie hackers in Europe or with European users. Their CAX11 (2 ARM vCPUs, 4 GB RAM) costs €3.79/month (~$4.10). The CX22 (2 AMD vCPUs, 4 GB RAM) is €4.35/month. Both include 20 TB of outbound traffic. That traffic allowance alone destroys the economics of AWS or GCP at equivalent specs.

For US-based projects or US users, Hetzner's Ashburn, VA location works fine. Latency to East Coast users is competitive. If you need West Coast coverage, Vultr's $6/month plan (2 vCPUs, 2 GB RAM, Los Angeles) is the next best option — not as cheap as Hetzner, but better than DigitalOcean's equivalent.

Here's a quick comparison of the providers I'd actually consider:

Provider Plan vCPUs RAM Storage Monthly Cost Traffic Included
Hetzner CAX11 2 (ARM) 4 GB 40 GB NVMe ~$4.10 20 TB
Hetzner CX22 2 (AMD) 4 GB 40 GB NVMe ~$4.70 20 TB
Vultr Regular 1 1 GB 25 GB SSD $6.00 2 TB
DigitalOcean Basic 1 1 GB 25 GB SSD $6.00 1 TB
Linode (Akamai) Nanode 1 1 GB 25 GB SSD $5.00 1 TB

Hetzner wins on every metric that matters for budget hosting. The only real reason to avoid them is if you need a specific region they don't cover (they have Helsinki, Nuremberg, Falkenstein, Singapore, and Ashburn as of mid-2025).

One note on ARM: the CAX11 is ARM64 (Ampere Altra). Most Docker images support linux/arm64 now, and anything built with Go, Python, or Node compiles fine. The only gotcha is some older binary-only software that ships x86_64 only. Check before you commit.

Right-Sizing Your Server

The biggest budget mistake isn't picking the wrong provider — it's over-provisioning "just in case." A 4 GB RAM instance can comfortably run:

  • Postgres 16 (configured correctly — see below)
  • Redis 7
  • A Node.js or Python app with 2-3 workers
  • Nginx as a reverse proxy
  • Certbot for TLS

That's a full production stack for a small SaaS. The key word is "configured correctly."

Out of the box, Postgres will try to use memory aggressively. On a 4 GB box, set these in /etc/postgresql/16/main/postgresql.conf:

shared_buffers = 512MB
effective_cache_size = 1536MB
work_mem = 8MB
maintenance_work_mem = 128MB
max_connections = 50

This keeps Postgres from eating 2 GB of RAM on startup. max_connections = 50 is fine for a small app — if you're hitting that limit, you need a connection pooler (PgBouncer), not a bigger server.

For Redis, add this to /etc/redis/redis.conf:

maxmemory 256mb
maxmemory-policy allkeys-lru

This caps Redis at 256 MB and evicts least-recently-used keys when it hits the limit. For caching use cases, this is exactly what you want.

With these settings, a typical small SaaS app sits at 1.5–2 GB RAM usage under load, leaving headroom for traffic spikes.

The $10/Month Stack That Actually Works

Here's what I'd deploy today for a new project with a real budget constraint:

Server: Hetzner CAX11 (~$4.10/month) Domain + DNS: Cloudflare ($0 for DNS, $10/year for a .com — call it $0.83/month) Backups: Hetzner's built-in backup option adds 20% to the server cost ($0.82/month), giving you 7 daily snapshots Email: Resend free tier (3,000 emails/month free, $0) Object storage: Hetzner Object Storage if you need it ($0.023/GB, likely under $1/month for a small app)

Total: roughly $6–$7/month for a production-ready stack. Under $10 with some breathing room.

The one thing I'd add that most budget guides skip: a $4/month Hetzner Cloud Firewall. Wait, it's actually free — Hetzner's Cloud Firewalls cost nothing. Use them. Lock down everything except ports 22, 80, and 443 from the start. Don't expose Postgres or Redis to the internet. This isn't optional.

Docker vs. Bare Metal on a Budget VPS

This question comes up constantly. My take: use Docker Compose, not Kubernetes, not bare metal.

Bare metal (installing Postgres directly on the host) is slightly more memory-efficient but makes migrations painful. When you want to move to a bigger server or add a second app, you're untangling a mess of system packages and config files.

Kubernetes on a single $4 VPS is a joke. The control plane alone eats 500 MB–1 GB of RAM. Save Kubernetes for when you have at least 3 nodes and a real reason.

Docker Compose hits the sweet spot. Here's a minimal docker-compose.yml for a Node + Postgres + Redis stack:

version: '3.9'
services:
  app:
    image: your-app:latest
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/appdb
      REDIS_URL: redis://cache:6379
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      - db
      - cache

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

Note the 127.0.0.1:3000:3000 binding on the app service. This means the app is only accessible locally — Nginx proxies to it. Never bind directly to 0.0.0.0 on a production server unless you've thought hard about why.

Nginx config for the reverse proxy:

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Get your cert with: certbot --nginx -d yourdomain.com. Certbot handles auto-renewal via a systemd timer on Ubuntu 22.04+.

Backups: The Part Everyone Skips Until It's Too Late

I've seen indie hackers lose weeks of data because they assumed their VPS provider was handling backups. They weren't.

For self-hosting on a budget, here's the minimum viable backup setup:

Database dumps to object storage. A cron job that runs pg_dump and uploads to Hetzner Object Storage (or Backblaze B2 at $0.006/GB) every 6 hours. Here's the cron entry:

0 */6 * * * docker exec db pg_dump -U app appdb | gzip | aws s3 cp - s3://your-bucket/db/$(date +\%Y-\%m-\%d-\%H\%M).sql.gz --endpoint-url https://your-region.your-objectstorage-endpoint.com

Use the AWS CLI with Hetzner Object Storage — it's S3-compatible. Set a lifecycle rule to delete objects older than 30 days so storage costs stay flat.

Hetzner snapshot backups. Enable the 20% backup option in the Hetzner console. This snapshots your entire server daily. It's not a replacement for database dumps (snapshots are eventually consistent), but it's a fast recovery path if you accidentally delete something catastrophic.

Test your restores. Seriously. A backup you've never restored from is a backup you don't have.

Monitoring Without Paying for Datadog

Datadog starts at $15/host/month. For a $4 server, that's absurd.

For basic monitoring, I use two free tools:

Uptime Kuma — self-hosted uptime monitoring. Run it as a Docker container on the same server (yes, it's fine for small setups). It pings your endpoints every 60 seconds and sends alerts via email, Telegram, or Slack. Free, open source, takes 5 minutes to set up.

Netdata — real-time server metrics (CPU, RAM, disk I/O, network). The free self-hosted version gives you everything you need. Install with their one-liner, then access the dashboard on port 19999 (firewall it to your IP only).

For error tracking, Sentry's free tier (5,000 errors/month) covers most small SaaS apps. No need to self-host Sentry unless you're handling sensitive data.

If you want to go deeper on monitoring your self-hosted stack, check out our guide to setting up Uptime Kuma on a VPS — it covers alerting configuration in detail.

Conclusion: What to Do Tomorrow

Self-hosting on a budget comes down to three decisions: pick Hetzner (CAX11 or CX22), deploy with Docker Compose instead of reinventing your stack every time, and automate backups before you need them.

The $10/month ceiling is real. I've been running production SaaS apps under that number for years. The money you save compounds — $90/month versus a $100 managed platform is $1,080/year you keep. That's a real number for a bootstrapped project.

Here's your one action for tomorrow: spin up a Hetzner CAX11, deploy the Docker Compose stack above, set up Certbot, and enable the 20% backup option. You'll have a production-ready server before lunch. If you want a step-by-step walkthrough of the initial server hardening, this post on VPS security basics covers exactly that.

Stop paying cloud tax on workloads that fit in 4 GB of RAM.