Managed PostgreSQL
Provision a managed PostgreSQL database for your application with zero configuration.
Overview
sota.io can provision a dedicated PostgreSQL 17 instance for each project. The database includes automatic connection pooling via PgBouncer, daily backups with 7-day retention, and auto-injected credentials.
Provisioning
Auto-Provisioning
Databases are auto-provisioned on your first deploy. The deploy engine calls ensureDatabase automatically, so you do not need to provision manually. The DATABASE_URL will be injected into your app container before it starts.
Via API
If you want to provision a database before your first deployment, you can use the API explicitly:
curl -X POST https://api.sota.io/v1/projects/550e8400-.../database \
-H "Authorization: Bearer <token>"
That is it. No configuration needed.
What Happens
- A PostgreSQL 17 container is created (
sota-db-{slug}) - A PgBouncer sidecar is started for connection pooling (
sota-pgbouncer-{slug}) - A cryptographically random password is generated
DATABASE_URLis automatically injected into your app container
Connecting
The DATABASE_URL environment variable is available in your application without any manual configuration:
// Node.js with pg
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
# Python with psycopg2
import os
import psycopg2
conn = psycopg2.connect(os.environ["DATABASE_URL"])
DATABASE_URL Format
The DATABASE_URL injected into your app follows this format:
postgresql://app_{slug}:{password}@sota-pgbouncer-{slug}:5432/{slug}?sslmode=disable
| Component | Value | Example |
|---|---|---|
| User | app_{slug} | app_my-app |
| Password | 32 hex chars (auto-generated) | a1b2c3... |
| Host | sota-pgbouncer-{slug} | sota-pgbouncer-my-app |
| Port | 5432 | 5432 |
| Database | {slug} | my-app |
| SSL Mode | disable | See Security |
The connection URL points to PgBouncer (port 5432), not directly to PostgreSQL. This provides automatic connection pooling.
Connection Pooling
Every database includes PgBouncer for connection pooling:
| Setting | Value |
|---|---|
| Pool Mode | Transaction |
| Default Pool Size | 20 |
| Max Client Connections | 100 |
This means your application can open up to 100 connections, and PgBouncer will multiplex them over 20 actual PostgreSQL connections.
Backups
Databases are automatically backed up daily:
| Feature | Detail |
|---|---|
| Method | pg_dump via Docker exec |
| Compression | gzip |
| Schedule | Every 24 hours |
| Retention | 7 days |
| Storage | Server local disk |
List Backups
curl https://api.sota.io/v1/projects/550e8400-.../database/backups \
-H "Authorization: Bearer <token>"
Trigger Manual Backup
curl -X POST https://api.sota.io/v1/projects/550e8400-.../database/backups \
-H "Authorization: Bearer <token>"
Creates an on-demand backup in addition to the automatic daily backups.
Security
- Database containers run on
runc(not gVisor) as trusted system services - Passwords are generated using
crypto/rand(32 hex characters) - The
DATABASE_URLis encrypted at rest using the same AES-256-GCM encryption as other environment variables - Network access is restricted to containers on the same Docker network
Why sslmode=disable?
The DATABASE_URL uses sslmode=disable. This is safe because of the network architecture:
- All containers (your app, PgBouncer, PostgreSQL) run on the same Docker bridge network (
traefik-public) on the same physical host - Traffic between containers never leaves the machine or traverses any external network
- There is no network path where an attacker could intercept the connection
- PgBouncer uses
AUTH_TYPE=plainfor the same reason -- all communication is internal-only
Despite SSL being disabled for the internal connection, credentials are still protected:
- Passwords are cryptographically strong (32 hex characters generated via
crypto/rand) - The
DATABASE_URLenvironment variable is encrypted at rest using AES-256-GCM - Database passwords are never exposed via the API