Docs · Guides
First Postgres in 10 minutes
Self-hosted Postgres 16 on a CloudNx VM. Production-grade defaults, a dedicated data volume, and a one-line backup cron. Until Managed Postgres reaches your region, this is the recommended path.
1. Provision
cloudnx instance create \
--name pg-01 \
--type cnx.m1.large \
--image ubuntu-24.04 \
--ssh-key my-key
cloudnx volume create --name pg-data --size 100
cloudnx volume attach pg-data --instance pg-01cnx.m1.large (4 vCPU, 8 GiB) handles ~500 RPS of typical OLTP. A 100 GB data volume is detachable for restore drills.
2. Install Postgres on the new VM
ssh ubuntu@<your-vm-ip>
sudo apt-get update
sudo apt-get install -y postgresql-16 postgresql-contrib3. Move data dir to the attached volume
# Format and mount the volume:
sudo mkfs.ext4 /dev/vdb
echo "/dev/vdb /var/lib/postgresql ext4 defaults,noatime 0 2" | sudo tee -a /etc/fstab
sudo systemctl stop postgresql
sudo rsync -av /var/lib/postgresql/ /tmp/pg-old/
sudo mount /var/lib/postgresql
sudo rsync -av /tmp/pg-old/ /var/lib/postgresql/
sudo chown -R postgres:postgres /var/lib/postgresql
sudo systemctl start postgresql4. Harden access
# Listen on private network only (not the public IP):
sudo sed -i "s/^#listen_addresses.*/listen_addresses = '10.10.0.0\/24,localhost'/" \
/etc/postgresql/16/main/postgresql.conf
# Force md5/scram passwords (not 'trust'):
echo "host all all 10.10.0.0/16 scram-sha-256" | sudo tee -a /etc/postgresql/16/main/pg_hba.conf
sudo systemctl reload postgresql5. Create a non-superuser app user
sudo -u postgres psql <<EOF
CREATE USER myapp PASSWORD '<long-random-passphrase>';
CREATE DATABASE myapp_prod OWNER myapp;
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT ALL ON SCHEMA public TO myapp;
EOF6. Firewall: only your app VMs can reach Postgres
cloudnx firewall add pg-01 tcp 5432 --cidr 10.10.0.0/24
# Nothing else — Postgres should never be reachable from the public internet.7. Daily logical backup to Object Storage
See the full pattern in Backup strategy. Minimal version:
# /etc/cron.d/pg-daily-backup
0 2 * * * postgres pg_dumpall | gzip | \
aws s3 --endpoint-url https://s3.cloudnx.in cp - \
s3://my-backups/pg-$(date +%Y%m%d).sql.gz8. Continuous WAL streaming (optional, recommended for > 10 GB databases)
For RPO-near-zero, run pgBackRest to ship WAL segments to Object Storage every minute. Point-in-time-recovery to any second in the last 7 days:
sudo apt-get install -y pgbackrest
sudo mkdir -p /etc/pgbackrest /var/lib/pgbackrest
# /etc/pgbackrest/pgbackrest.conf — see pgbackrest docs for the full template.
# Then on cron: pgbackrest --stanza=main backup9. Verify your runbook
Quarterly, run the restore drill on a throwaway VM. If you can’t restore in < 30 minutes, your backup strategy isn’t working — fix it before you need it.
When to switch to Managed Postgres
Self-host is great until one of these starts to hurt:
- Engineering hours spent on Postgres-ops > 1 hour/week.
- You need HA failover (primary + standby).
- You need point-in-time recovery without becoming a pgBackRest expert.
At that point, create a managed cluster. Migration is one pg_dump | psql.