Data
Postgres in production: the checklist
Where to run it, how to back it up, how to prove the backup works, how to connect to it, and what to watch once real users depend on it.
The one thing you cannot redeploy
Every other part of your application can be rebuilt from the repository. The database cannot. That single fact should shape every decision in this guide: when in doubt, choose the option that makes data loss less likely and recovery more boring.
Managed or in-cluster?
You can run Postgres yourself on Kubernetes, or use your provider’s managed service (Amazon RDS or Aurora, Cloud SQL, Azure Database for PostgreSQL).
Choose managed for almost every production workload at a small or medium company. You get automated backups, point-in-time recovery, minor version patching, failover options and storage that grows: all things you would otherwise have to build and operate.
Consider in-cluster for development, previews and test environments, where cost and speed matter more than durability, or when you have specific requirements and the people to operate them.
Kapten’s default for production is a managed database in your cloud account, planned by the Bosun. In-cluster databases are a reasonable choice for preview environments.
Backups
Turn on automated backups on day one and decide:
- Retention. How far back can you restore? A week is a floor for most products; longer if you have compliance or contractual requirements.
- Point-in-time recovery (PITR). Restore to any moment within the retention window, not just to last night. This is what saves you from the bad
DELETEat 14:32. - Copies outside the primary region or account, if losing the region or the account is a risk you need to cover.
The restore test
A backup you have never restored is a hope, not a backup. Schedule a restore test:
- Restore the latest backup, or a point in time, into a separate instance.
- Run a few queries your application depends on.
- Record how long it took, end to end.
- Delete the test instance.
- RestorebosunLatest backup or a point in time, into a new instance.
- Gate: Results matchQueryReads the application depends on.
- RecordlookoutEnd-to-end time, written down.
- DeletepurserRemove the test instance.
Do it before launch and then regularly. The time it takes is your real recovery time, and it is usually longer than people expect.
Connections and pooling
Postgres handles each connection with a dedicated process, and the number it can hold is finite. Applications that scale horizontally (more replicas, serverless functions, background workers) can exhaust it quickly.
- Size each service’s pool deliberately. Total connections across all replicas must stay below the database’s limit, with room left for migrations and administration.
- Use a connection pooler (PgBouncer or your provider’s proxy) when you have many replicas or short-lived processes.
- Set statement and idle timeouts so a stuck client cannot hold connections forever.
| Point | Connections in use (conns) | Event |
|---|---|---|
| 06:00 | 42 | |
| 07:00 | 45 | |
| 08:00 | 48 | |
| 09:00 | 52 | |
| 10:00 | 50 | |
| 11:00 | 88 | Deploy: workers ×3 |
| 12:00 | 96 | Alert: near limit |
| 13:00 | 94 | |
| 14:00 | 61 | Event: pooler on |
| 15:00 | 40 | |
| 16:00 | 38 | |
| 17:00 | 41 |
Migrations
- Run migrations as an explicit step before a release, not when the app starts.
- Keep each migration compatible with the version of the code currently running, so rollbacks stay possible. The rollbacks guide covers the expand/contract pattern.
- Be careful with operations that lock large tables: adding an index without
CONCURRENTLY, adding a column with a volatile default, changing a column type. Test them on a copy with realistic data. - Rehearse risky migrations on a restored copy of production. The migration rehearsal workflow automates exactly that.
Sizing
Start smaller than you think, and watch:
- CPU sustained high usually means a missing index or an expensive query, not a need for a bigger instance.
- Memory determines how much of your working set stays cached. Falling cache hit rates are an early warning.
- Storage grows with data, indexes, and write-ahead logs. Enable automatic storage growth and alert well before the limit.
- IOPS limits on some storage classes can cap performance long before CPU does.
Resize based on evidence. The Purser flags databases that are consistently oversized as well as the ones that are close to their limits.
Private networking
Your database should not have a public address.
- Put it in private subnets, reachable only from the application’s network.
- Restrict access with security groups or firewall rules to the services that need it.
- Require TLS for connections.
- For administration, use a bastion, a VPN or your provider’s session manager rather than opening the database to the internet “for a moment”.
- Web connects to Pooler (app user).
- Worker connects to Pooler.
- Pooler connects to Postgres (tls).
- Postgres connects to Snapshots + WAL (automated).
The Warden treats a database with a public endpoint as a high-severity finding.
Users and permissions
- The application connects with a dedicated user that can read and write its tables, and nothing else.
- Migrations run with a separate, more privileged user.
- People get read-only access by default; write access to production data is the exception, and is logged.
- Never use the master user in application configuration.
What to monitor
Start with a small set of signals:
- Connections in use versus the limit.
- CPU, memory, free storage, and IOPS.
- Replication lag, if you have replicas.
- Slow queries: enable the slow query log or
pg_stat_statements. - Backup success and the age of the latest restorable point.
Alert on free storage, connection saturation and failed backups first. Those are the three that turn into outages. The observability starter kit covers the application side.
Before launch
- Managed instance in private subnets, TLS required.
- Automated backups and PITR enabled, retention agreed with the data owner.
- One successful restore test, with the time recorded.
- Application user with least privilege; separate migration user.
- Connection pool sized; pooler in place if needed.
- Alerts on storage, connections and backups.
- A written plan for restoring after a bad migration or deletion.
Kapten plans and provisions managed databases in your account during early access; supported engines and providers are confirmed during onboarding. See the production-readiness review to check an existing environment against this list.
Build with Kapten
Start with one service.
Build from there.
Tell us what you want to deploy. We review each request and follow up about fit and onboarding on your preferred cloud.
Free during early access. No card required. Cloud usage billed separately.