Skip to main content
tutorial··7 min read

Deploy PostgreSQL on a Hetzner VPS in 5 minutes

A complete walkthrough: provision a Hetzner Cloud server, connect it to Vessl, and deploy a production-ready PostgreSQL instance with SSL, persistent storage, and automated backups.

By Vessl Team

If you've ever priced out a managed PostgreSQL instance on AWS RDS or Google Cloud SQL, you know the math gets uncomfortable fast. A db.t3.medium with 2 vCPU and 4 GB RAM on RDS costs around $60/month before storage, IOPS, and backups. The same hardware on a Hetzner Cloud CX22 is €4.51/month — roughly 12x cheaper.

The catch is that managed Postgres handles a lot for you: provisioning, upgrades, backups, point-in-time recovery, monitoring. Self-hosting trades that for control and price.

This post shows how to get most of the convenience back — with Vessl, a Hetzner VPS, and about 5 minutes of clicking. The result: a production Postgres instance that survives reboots, gets automated backups, and is reachable only from your application network.

What you'll need

  • A Hetzner Cloud account (no minimum spend, hourly billing)
  • A Vessl account (free tier works)
  • A domain name if you want public reachability — optional for database-only deployments

The total bill comes to about €4.51/month for the VPS + Vessl's free tier. No managed service fees.

Step 1: Provision the VPS

In the Hetzner Cloud console, create a new project and click Add Server:

  • Location: pick the region closest to your users (Falkenstein, Helsinki, Ashburn, Hillsboro, Singapore)
  • Image: Ubuntu 24.04 LTS
  • Type: CX22 (2 vCPU, 4 GB RAM, 40 GB disk) is plenty for most app databases
  • Networking: keep IPv4 enabled — Vessl needs SSH over IPv4 to reach the box
  • SSH keys: paste your public key so root can log in

Hit Create & Buy Now. Hetzner provisions the server in about 10 seconds and gives you a public IP.

Step 2: Connect the server to Vessl

Vessl is agentless — it operates the server over plain SSH from the control plane. No daemon, no socket exposed, no inbound tunnel. The same SSH key you used during VPS creation is what Vessl uses to provision and deploy. If you ever stop using Vessl, you remove the key and the server is back to a regular Ubuntu box.

Open the Vessl dashboard, head to Servers, and click Connect Server. The wizard runs in two steps:

Step 1 — Network Identity. Give the server a name (e.g. prod-db-1), enter the public IP from Hetzner, and the SSH user/port (root / 22 if you used the defaults).

Step 2 — SSH Key. Pick a key from your Vessl wallet. You have three options:

  • Vessl-generated — Vessl creates a fresh ED25519 keypair, shows the private key to you exactly once for safekeeping, and registers the public half. Paste the public key into Hetzner's Cloud Console (or ~/.ssh/authorized_keys on the box) before continuing.
  • Manual upload — paste a key pair you already trust.
  • Auto-setup — if you have a temporary SSH password for the server, Vessl can install a generated key for you in a single step. After it's done, switch off password auth on the server and Vessl uses the key from then on.

Click Check Reachability to confirm Vessl can reach the SSH port, then Verify SSH Credentials to confirm the key works. Both should turn green within a few seconds.

Hit Start Provisioning. Vessl runs the host bootstrap over SSH:

  1. Validates the host (OS version, kernel, disk, memory)
  2. Installs Docker and the runtime skeleton
  3. Sets up the gateway layer for routing inbound traffic
  4. Hardens the host (UFW defaults, fail2ban, kernel sysctls)

Provisioning takes about 90 seconds end-to-end, with each phase reported live in the dashboard. When it flips to ready, the server is yours to deploy onto.

Step 3: Deploy PostgreSQL

In the Vessl dashboard, head to Templates and pick PostgreSQL. The template panel asks for two things:

  • Project name: where this database lives (you can put multiple services in one project)
  • Postgres version: 16 by default — pin a different tag if you need pgvector or a vendor variant

Click Deploy. Vessl:

  1. Pulls the official Postgres image on the VPS
  2. Generates a strong superuser password
  3. Mounts a Docker volume for /var/lib/postgresql/data so the data survives container restarts
  4. Wires the service into the project's private network — no public port exposed by default

The container is healthy within about 15 seconds. The dashboard shows the auto-generated DATABASE_URL you'll plug into your application.

Step 4: Connect from your application

If your app is also deployed on Vessl in the same project, you don't need to copy anything manually. Vessl injects DATABASE_URL (and the individual DB_HOST, DB_USERNAME, DB_PASSWORD vars) into the application container automatically. Frameworks like Laravel, Rails, Django, and Prisma pick them up on first boot.

If your app runs elsewhere, you have two options:

  • Recommended: deploy the app to the same Vessl project (even if it's just a thin proxy or worker). The internal network keeps Postgres isolated and traffic doesn't leave the host.
  • Direct exposure: open port 5432 on the Hetzner firewall to specific source IPs only and connect over the public IP. Vessl's HTTP routing layer doesn't terminate Postgres traffic — Postgres speaks its own wire protocol, not HTTP — so this is a manual ufw rule on the server, not a dashboard toggle.

For most production stacks, keep Postgres internal-only. Public exposure is fine for cases where you genuinely need cross-region access, but always pin the source IP and require sslmode=require on the client.

Step 5: Set up backups

Persistent volumes survive container restarts but don't survive disk loss. For real durability you need off-server backups.

Managed backups (scheduled pg_dump to S3-compatible storage) are on the Vessl roadmap but not yet shipped. Until they land, the standard pattern is a cron-driven pg_dump that ships the archive to Cloudflare R2 (cheapest at $0.015/GB/month, no egress fees) or any S3-compatible bucket.

Vessl's Cron tab on the server lets you schedule the dump on the host directly:

# Runs daily at 02:00, keeps last 30 dumps in R2
docker exec vessl-postgres pg_dump -U postgres postgres | \
  gzip | \
  aws s3 cp - s3://my-backup-bucket/postgres-$(date +%F).sql.gz \
    --endpoint-url https://<account>.r2.cloudflarestorage.com

Pair it with a lifecycle rule on the bucket to expire archives after 30 days. We'll cover the full setup — including encryption-at-rest and restore drills — in a follow-up post.

What about high availability?

A single Postgres instance is a single point of failure. If the Hetzner VPS goes down, your database goes with it. For most early-stage apps that's an acceptable trade — Hetzner's uptime is genuinely good (99.9%+ on most CX-class servers in our experience), and Vessl restarts the container automatically if Postgres crashes.

When you outgrow single-instance, the upgrade path is:

  1. Add a read replica on a second Hetzner VPS — also one click in Vessl, but on the roadmap rather than shipped today
  2. Manual failover until automatic failover ships
  3. Geographic redundancy by replicating to a different Hetzner region or another provider entirely

Most apps go years before they need any of this. Don't build it before you have to.

What it actually costs

Adding it all up for a real production setup:

Component Cost / month
Hetzner CX22 VPS €4.51
Cloudflare R2 backups (5 GB) $0.08
Domain (Cloudflare Registrar, .com) $1.00
Vessl free tier $0
Total €5.50 ($6)

A managed Postgres of equivalent specs on AWS RDS, Google Cloud SQL, or Supabase Pro lands somewhere between $50 and $90/month. You're trading 10x the price for managed convenience.

If your team would rather spend that delta on engineering than on a managed service, self-hosting on Vessl is a fair trade. If your time costs more than $50/hour and you'd rather not think about Postgres at all, stay managed — that's also a valid call.

Next steps

Self-hosting doesn't have to mean writing Ansible playbooks. Pick the right control plane, and you get the cheap-and-yours benefits without the operational tax.

Ready to ship?

Bring your VPS. We'll handle the rest.

Vessl deploys containers, provisions SSL, and ships zero-downtime updates to any Linux server you own. No DevOps team required.

Start for free