Managed PostgreSQL
A managed, relational database you can provision and link to your projects in seconds. PostgreSQL is one of the managed database services Temps runs as a container on your own infrastructure.
Provision and manage PostgreSQL three ways: the dashboard, the @temps-sdk/cli command-line tool (Manage from the CLI), or the REST API. If you use an AI coding agent (Claude Code, Cursor, Windsurf), install the temps-cli skill and just describe what you want — the agent already knows these commands.
Creating a PostgreSQL Instance
Via Dashboard:
- Navigate to Services → Create Service.
- Select PostgreSQL.
- Choose the image:
- PostgreSQL 18 (Managed + WAL-G) — the recommended default. This image bundles WAL-G for point-in-time recovery (PITR).
- Custom image — bring your own (e.g.
postgres:17-alpine). Custom images support basic snapshot backups only — no PITR.
- Click Create Service.
On creation Temps:
- Pulls the image
gotempsh/postgres-walg:18-bookworm(the managed default). - Auto-assigns a host port starting from 5432 and creates a dedicated Docker volume for the data directory.
- Auto-generates a
postgressuperuser password.
There is no fixed storage quota — the data volume grows against the host disk. To cap memory and CPU per container, see Resource Limits below. For a high-availability topology, Temps also offers a PostgreSQL cluster (pg_auto_failover) — provision it from the same Create Service flow.
Via the CLI:
Authenticate once, then create the service in a single command. PostgreSQL requires the database, username, and password parameters — set them with repeatable -s key=value flags:
Create a PostgreSQL 18 database
# Log in to your Temps server (stores credentials in ~/.temps)
bunx @temps-sdk/cli login
# Create a PostgreSQL 18 instance — generate a strong password inline
bunx @temps-sdk/cli services create \
-t postgres \
-n my-database \
-s version=18-alpine \
-s database=appdb \
-s username=appuser \
-s password="$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 28)" \
-y
version=18-alpine provisions a custom postgres:18-alpine image — snapshot backups only, no PITR. For the managed WAL-G image with point-in-time recovery, create the service from the dashboard and pick PostgreSQL 18 (Managed + WAL-G). The password you set is shown again any time via services show (see below), so you never have to store it yourself.
Manage from the CLI
Once created, the same @temps-sdk/cli tool covers the full lifecycle. Every list/show command also accepts --json for scripting.
Inspect & connect
# List every service and its status (note the numeric ID)
bunx @temps-sdk/cli services list
# Show full details, including the connection string
bunx @temps-sdk/cli services show --id 10
Link to a project (injects POSTGRES_URL, etc.)
# Link the service to a project so its env vars are injected on deploy
bunx @temps-sdk/cli services link --id 10 --project-id 5
# See exactly which variables were injected
bunx @temps-sdk/cli services env --id 10 --project-id 5
# Unlink when you no longer need it
bunx @temps-sdk/cli services unlink --id 10 --project-id 5
Lifecycle
# Stop / start the container
bunx @temps-sdk/cli services stop --id 10
bunx @temps-sdk/cli services start --id 10
# Upgrade the image version
bunx @temps-sdk/cli services upgrade --id 10 -v postgres:18-alpine
# Permanently remove the service (destroys data — see the warning below)
bunx @temps-sdk/cli services remove --id 10 -f
The same services create -t <type> flow works for the other managed databases too — mongodb, redis, and s3. Each type validates its own required parameters and tells you what's missing. Prefer natural language? With the temps-cli skill installed, ask your AI agent "create a Postgres 18 database and link it to my project" and it runs these commands for you.
Connecting to PostgreSQL
Node.js/TypeScript
import { Pool } from 'pg';
// POSTGRES_URL is injected automatically when the service is linked
const pool = new Pool({ connectionString: process.env.POSTGRES_URL });
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
Python
import os
import psycopg2
conn = psycopg2.connect(os.environ['POSTGRES_URL'])
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))
Connection string format: postgresql://<user>:<password>@<host>:5432/<database>
Connections happen over the Temps internal network using the container name as the host — the database port is not exposed to the public internet. Managed PostgreSQL starts with SSL disabled by default since traffic stays on the internal network.
Linking to Projects
When you link a PostgreSQL service to a project + environment, Temps creates a per-tenant database named <project>_<environment> on the instance and injects connection details into that environment:
| Variable | Description |
|---|---|
POSTGRES_URL | Full connection string |
POSTGRES_HOST | Container hostname |
POSTGRES_PORT | Port (5432) |
POSTGRES_DB | Per-tenant database name (also exposed as POSTGRES_DATABASE / POSTGRES_NAME) |
POSTGRES_USER | Username |
POSTGRES_PASSWORD | Password |
Access in your app
const pool = new Pool({ connectionString: process.env.POSTGRES_URL });
Each linked project gets its own logical database, but projects connect with the same instance credentials — Temps isolates by database, not by per-project user.
PostgreSQL WAL Health
Managed PostgreSQL services run a read-only WAL/archive health probe as part of the background health monitor (a ~30-second poll). The probe never writes to your database — it only reads PostgreSQL system views and inspects the pg_wal/ directory — and the result is stored on the service so the dashboard can surface problems before they fill your disk.
Why this exists: in earlier versions, PostgreSQL containers could run with archive_mode=on even when no archive destination was configured. A service whose archive_command was never set would accumulate WAL forever. The WAL health probe detects this condition (and several others) and flags it in the UI.
What the probe checks
The probe reads pg_ls_waldir(), pg_settings (max_wal_size, archive_mode, archive_command), replication-slot state, and the archiver status, then computes warnings. Each warning carries a severity of warning or critical:
| Condition | Triggers when | Severity |
|---|---|---|
| WAL bloat | pg_wal size is at least 3× max_wal_size | critical at 10×, otherwise warning |
| Stale replication slot | An inactive slot is retaining ≥ 3× max_wal_size of WAL | critical |
| Archive backlog | At least 100 .ready segments in pg_wal/archive_status | warning |
| WAL not recycled | The oldest WAL segment is at least 24h old while pg_wal is bloated | warning |
Reading the snapshot
The latest snapshot is exposed at:
GET /api/external-services/{id}/wal-health
It requires the ExternalServicesRead permission and returns a PostgresWalHealth snapshot (pg_wal size, max_wal_size, archive_mode, archive_command, archive backlog, archiver failure counts, stale slots, oldest-WAL age, and the computed warnings). The web console renders this as a warning panel on the service detail page.
Backups & Durability
The managed gotempsh/postgres-walg image bundles WAL-G, which archives WAL to your configured S3 destination and enables point-in-time recovery (PITR) — PostgreSQL is the only managed service that supports PITR. Custom images fall back to basic snapshot backups with no PITR.
Backups run on a schedule you configure, and each schedule has its own retention window (in days) — Temps does not impose a fixed default retention. Restores are driven from the CLI:
Restore via the Temps CLI
# Inspect what a service supports
temps services restore capabilities
# List backups available from an S3 source
temps services restore list-backups
# Start a restore (point-in-time for PostgreSQL)
temps services restore start
A confirmed write to PostgreSQL is durable — data is fsynced before the client receives acknowledgment. The built-in WAL health probe watches for WAL bloat and misconfigured archiving so a stalled archive destination can't silently fill your disk.
Monitoring
View live metrics and health in the dashboard's Services section — CPU & memory usage, connection count, disk usage, uptime, and slow-query detection. Service-level monitoring is not yet exposed through the CLI.
Resource Limits
You can cap how much memory, swap, and CPU the service's container is allowed to use, and inspect what it's actually consuming. A service with no limits set runs unconstrained (the default). The same controls apply to every managed service type — PostgreSQL (standalone and cluster), MongoDB, Redis, RustFS, and S3.
The service detail page shows a Resources card that polls runtime status every 30 seconds and live usage every 5 seconds, plus an Edit limits dialog.
Editing limits
Open Services → select the service → Resources card → Edit limits. The dialog has independent Memory and CPU toggles:
| Field | Meaning | Empty / off |
|---|---|---|
| Memory | Hard memory cap, in MiB | Unlimited |
| Swap | Extra swap above the memory cap, in MiB | No extra swap |
| CPU | CPU cap, in cores | Unlimited |
The Swap input means swap in addition to the memory limit — Memory = 512 and Swap = 256 lets the container use 512 MiB of RAM plus 256 MiB of swap. Internally Temps submits memory + swap to Docker. When a memory cap is set and the working set exceeds it, the kernel OOM killer terminates the container — that kill never reaches the app's own logs, so the oom_killed flag on the runtime endpoint is often the only signal.
Endpoints
All served under the /api prefix:
GET /api/external-services/{id}/runtime
GET /api/external-services/{id}/stats
PATCH /api/external-services/{id}/resources
GET …/runtime(ExternalServicesRead) — a per-containerdocker inspectsnapshot:status,restart_count(a steady climb signals a crash loop),oom_killed, lastexit_code, image, and the limits actually applied (so you can detect drift between configured and live caps).GET …/stats(ExternalServicesRead) — a one-shot CPU/memory usage sample per container, cheap enough to poll every 5–10s.memory_percentis measured against the memory limit when one is set, or against host RAM otherwise.PATCH …/resources(ExternalServicesWrite) — persists the caps to the service's encrypted config and live-applies them via Docker's update API (no restart needed). A request where every field isnullremoves all limits.
Security
- Name
Network Isolation- Description
- Reachable only from containers on the same Temps internal network — the port is never exposed to the public internet
- Apps connect using the container name as the host, over private networking
- Credentials are auto-generated at creation; you never have to choose a password
- Name
Credential Handling- Description
- Temps injects credentials as environment variables when the service is linked — never hard-code them
- Credentials are stored encrypted at rest (see Encryption below)
- Rotate them from Services → select service → Rotate Credentials; linked projects update automatically
Encryption
Credentials (passwords, access keys) are encrypted with AES-256-GCM before being written to the Temps database. The key lives at ~/.temps/encryption_key — protect this file and back it up separately from the database.
Database contents are not encrypted at the application layer by default. For encryption at rest, enable it at the infrastructure level — encrypted volumes or LUKS on bare metal; Temps does not manage disk encryption. In transit, traffic stays on the internal Docker network and is never exposed to the public internet (managed PostgreSQL starts with SSL disabled for this reason).
Deleting the Service
Deleting the service permanently destroys all data and backups associated with it. This cannot be undone.
- Download a backup — Services → select the service → Backups → download the latest.
- Unlink all projects so no running app still holds a reference to the credentials.
- Delete the service from its settings page and confirm.
Pricing & Ownership
Because the service runs on your own infrastructure, there are no per-GB storage charges, connection caps, or metered bandwidth fees. A recommended starting allocation:
| Tier | Allocation |
|---|---|
| Small | 1 GB RAM, 10 GB storage |
| Medium | 2 GB RAM, 50 GB storage |
| Large | 4 GB+ RAM, 200 GB+ storage |
PostgreSQL is built in — Vercel bills Vercel Postgres separately, Netlify is external-only, and Railway includes it. Your data never leaves your server; Temps (the company) has no access to it. See Data Ownership & Privacy for the full policy, including GDPR.