Managed Services

Temps provides fully managed database and storage services that you can provision and link to your projects in seconds. Unlike platforms like Vercel that require external services, Temps includes everything you need.


Available Services

Temps offers three types of managed services that integrate seamlessly with your projects:

  • PostgreSQL — Production-ready relational database
  • Redis — High-performance caching and pub/sub
  • S3 Storage — Object storage compatible with MinIO and AWS S3

All services are provisioned on your infrastructure, giving you complete control over your data.


PostgreSQL Database

Creating a PostgreSQL Instance

Provision a production-ready PostgreSQL database with automatic backups and high availability:

Via Dashboard:

  1. Navigate to ServicesCreate Service
  2. Select PostgreSQL
  3. Choose version (13, 14, 15, or 16)
  4. Configure storage size and instance resources
  5. Click Create Database

Configuration Options:

OptionDescriptionDefault
versionPostgreSQL version (13-16)16
storageDisk space allocation10GB
memoryRAM allocation1GB
replicasNumber of read replicas1
backupsAutomatic daily backupsEnabled

Connecting to PostgreSQL

Once created, Temps generates a connection string you can use in your applications:

Node.js/TypeScript

import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: true
  }
});

// Query example
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);

Python

import psycopg2

conn = psycopg2.connect(
    os.environ['DATABASE_URL'],
    sslmode='require'
)

cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))

Linking to Projects

Link your PostgreSQL database to one or more projects:

Via Dashboard:

  1. Open the service details page for your PostgreSQL instance.
  2. Click Link to Project, select the project and environment.
  3. Temps injects the connection string as an environment variable automatically.

Access in your app

// Automatically available as environment variable after linking
const databaseUrl = process.env.DATABASE_URL;
const db = new Database(databaseUrl);

Database Migrations

Run migrations during deployment by configuring build hooks:

temps.json

{
  "build": {
    "command": "npm run build",
    "beforeDeploy": [
      "npm run db:migrate"
    ]
  }
}

PostgreSQL WAL Health

Standalone managed PostgreSQL services run a read-only WAL/archive health probe on every background health-poll cycle. 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.

What the probe checks

The probe computes warnings for the following conditions. Each warning carries a severity of either warning or critical:

ConditionTriggers whenSeverity
WAL bloatpg_wal size is at least 3× max_wal_sizecritical at 10×, otherwise warning
Stale replication slotAn inactive/stale replication slot is retaining a large amount of WALcritical
Archive backlogAt least 100 .ready segments in pg_wal/archive_statuswarning
archive_mode without commandarchive_mode is on/always but archive_command is empty or a no-op (/bin/true, true)warning
WAL not recycledThe oldest WAL segment is at least 24h old and pg_wal is bloated (≥ 3× max_wal_size)warning

When any warning is present, the background monitor downgrades an Operational service to Degraded. It never escalates a service that is already Down.

Reading the snapshot

The latest snapshot is exposed at:

GET /api/external-services/{id}/wal-health

This is a plugin route served under the /api prefix. It requires the ExternalServicesRead permission and returns:

StatusMeaning
200A PostgresWalHealth snapshot (pg_wal size, max_wal_size, archive_mode, and any warnings)
404Service not found, not a PostgreSQL service, or no snapshot has been recorded yet

The web console renders the snapshot on the service detail page as a warning panel showing pg_wal size, max_wal_size, archive_mode, and one row per warning. Two of the warnings include a copy-to-clipboard SQL snippet:

WarningRemediation snippet
Stale replication slotSELECT pg_drop_replication_slot('<slot>');
WAL not recycledCHECKPOINT;

The remaining warnings (WAL bloat, archive backlog, and archive_mode without command) show plain-language guidance instead — for example, the archive_mode-without-command warning tells you to Stop/Start the service so it self-repairs (see below).

Safe archive_mode defaults

New PostgreSQL services now start with archive_mode=off. WAL archiving is only turned on once a destination is actually configured:

  • When you enable WAL archiving, Temps writes the WAL-G environment file, sets archive_command, and then recreates the container with archive_mode=on baked into its startup command.
  • On every start, the service reconciles: it probes the service volume for the WAL-G config and compares the intended archive_mode against the running container's command. If they disagree, it recreates the container with the correct setting. This means a simple Stop → Start auto-repairs any existing service stuck in the old "archive on, no destination" state.

After such a reconciling recreate, Temps refreshes the WAL health snapshot inline, so the dashboard reflects the new state within about a second instead of waiting for the next poll cycle.


Redis Caching

Creating a Redis Instance

Provision a high-performance Redis instance for caching, sessions, or pub/sub:

Via Dashboard:

  1. Navigate to ServicesCreate Service
  2. Select Redis
  3. Choose version (6 or 7)
  4. Configure memory allocation
  5. Click Create Cache

Configuration Options:

OptionDescriptionDefault
versionRedis version (6-7)7
memoryRAM allocation512MB
persistenceEnable RDB/AOFDisabled
evictionEviction policyallkeys-lru
maxclientsMax connections10000

Connecting to Redis

Node.js with ioredis

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// Set a value
await redis.set('session:123', JSON.stringify(sessionData), 'EX', 3600);

// Get a value
const data = await redis.get('session:123');

Python with redis-py

import redis
import os

r = redis.from_url(os.environ['REDIS_URL'])

# Set with expiration
r.setex('session:123', 3600, json.dumps(session_data))

# Get value
data = r.get('session:123')

Common Use Cases

  • Name
    Session Storage
    Description

    Store user sessions with automatic expiration:

    await redis.set(`session:${userId}`, sessionData, 'EX', 86400); // 24 hours
    
  • Name
    Rate Limiting
    Description

    Implement rate limiting with Redis counters:

    const count = await redis.incr(`rate:${ipAddress}`);
    if (count === 1) await redis.expire(`rate:${ipAddress}`, 60); // 60 seconds
    if (count > 100) throw new Error('Rate limit exceeded');
    
  • Name
    Caching
    Description

    Cache expensive queries or API responses:

    const cached = await redis.get(`cache:${key}`);
    if (cached) return JSON.parse(cached);
    
    const data = await fetchExpensiveData();
    await redis.set(`cache:${key}`, JSON.stringify(data), 'EX', 300); // 5 minutes
    
  • Name
    Pub/Sub
    Description

    Real-time messaging between services:

    // Publisher
    await redis.publish('notifications', JSON.stringify(message));
    
    // Subscriber
    redis.subscribe('notifications', (message) => {
      console.log('Received:', JSON.parse(message));
    });
    

S3 Object Storage

Creating S3 Storage

Provision MinIO-compatible object storage for files, backups, and assets:

Via Dashboard:

  1. Navigate to ServicesCreate Service
  2. Select S3 Storage
  3. Configure storage size
  4. Set bucket policy (private/public)
  5. Click Create Storage

Configuration Options:

OptionDescriptionDefault
sizeStorage allocation50GB
public-readPublic read accessfalse
versioningObject versioningDisabled
lifecycleAuto-delete rulesNone

Connecting to S3

Temps S3 is compatible with AWS S3 SDKs:

Node.js with AWS SDK v3

import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,
  region: 'us-east-1',
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey: process.env.S3_SECRET_KEY,
  },
});

// Upload a file
await s3.send(new PutObjectCommand({
  Bucket: 'media-storage',
  Key: 'uploads/image.jpg',
  Body: fileBuffer,
  ContentType: 'image/jpeg',
}));

// Download a file
const response = await s3.send(new GetObjectCommand({
  Bucket: 'media-storage',
  Key: 'uploads/image.jpg',
}));

Python with boto3

import boto3
import os

s3 = boto3.client(
    's3',
    endpoint_url=os.environ['S3_ENDPOINT'],
    aws_access_key_id=os.environ['S3_ACCESS_KEY'],
    aws_secret_access_key=os.environ['S3_SECRET_KEY'],
)

# Upload file
s3.upload_file('local-file.jpg', 'media-storage', 'uploads/image.jpg')

# Download file
s3.download_file('media-storage', 'uploads/image.jpg', 'downloaded.jpg')

Public URLs

Generate pre-signed URLs for temporary access:

import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const command = new GetObjectCommand({
  Bucket: 'media-storage',
  Key: 'uploads/private-document.pdf',
});

// URL expires in 1 hour
const url = await getSignedUrl(s3, command, { expiresIn: 3600 });

Service Management

Monitoring Service Health

View service metrics and health status in the dashboard:

  • CPU & Memory Usage: Real-time resource consumption
  • Connection Count: Active database/cache connections
  • Storage Usage: Disk space utilization
  • Uptime: Service availability percentage
  • Query Performance: Slow query detection (PostgreSQL)

All monitoring is available in the Services section of the dashboard. Service-level monitoring is not yet exposed through the CLI.

Backups

All services include automatic daily backups retained for 7 days (configurable up to 90 days):

Manual backups and restore are managed through the dashboard. You can also use the temps backup CLI commands, which read from and write to your configured S3 bucket:

List and restore backups via CLI

# List available backups stored in S3
temps backup list \
  --bucket-name my-temps-backups \
  --endpoint $S3_ENDPOINT \
  --access-key-id $S3_ACCESS_KEY_ID \
  --secret-access-key $S3_SECRET_ACCESS_KEY

# Restore a specific managed service from a backup
temps backup restore-service \
  --bucket-name my-temps-backups \
  --endpoint $S3_ENDPOINT \
  --access-key-id $S3_ACCESS_KEY_ID \
  --secret-access-key $S3_SECRET_ACCESS_KEY \
  --database-url $TEMPS_DATABASE_URL \
  --backup-id <backup-id-from-list> \
  --service-name production-db \
  --encryption-key $TEMPS_ENCRYPTION_KEY

Scaling Services

Scale your services as your needs grow through the dashboard (Services → select service → Scale). Adjust storage, memory, and replica count from the service settings panel. To cap how much memory and CPU a service's container may consume, see Per-Container Resource Limits below.


Per-Container Resource Limits

You can cap how much memory, swap, and CPU each managed service's container is allowed to use, and inspect what it's actually consuming. This applies to all managed/external service types — PostgreSQL (standalone and cluster), Redis, MongoDB, RustFS, and S3. Services created before this feature, or any service with no limits set, run unconstrained (the default).

The service detail page in the dashboard shows a Resources card that polls runtime status every 30 seconds and live usage every 5 seconds, plus an Edit limits dialog. Behind the dashboard are three REST endpoints.

Editing limits

Open Services → select the service → Resources card → Edit limits. The dialog has independent Memory and CPU toggles:

FieldMeaningEmpty / off
MemoryHard memory cap, in MiBUnlimited
SwapExtra swap above the memory cap, in MiB (not Docker's raw total)No extra swap
CPUCPU cap, in coresUnlimited

When you enable a memory cap, the dialog shows an explicit OOM warning: if the container's working set exceeds a hard memory limit, the kernel OOM killer terminates it — and that kill never reaches the application's own logs, so an oom_killed flag on the runtime endpoint is often the only signal that a memory limit was the cause.

Leaving every field unset removes all limits and returns the service to unconstrained operation.

Endpoints

All three are served under the /api prefix.

GET   /api/external-services/{id}/runtime
GET   /api/external-services/{id}/stats
PATCH /api/external-services/{id}/resources

GET /api/external-services/{id}/runtime — requires ExternalServicesRead. Returns a per-container snapshot from docker inspect: container status, restart_count (a steady climb signals a crash loop), the oom_killed flag, last exit_code, image, and the resource limits actually applied to the running container (so you can detect drift between configured and live caps). Standalone services return one member; clusters return one entry per member.

GET /api/external-services/{id}/stats — requires ExternalServicesRead. A one-shot CPU/memory usage sample per container (no streaming), cheap enough to poll on a 5–10s interval. cpu_percent uses Docker's standard formula; memory_percent is measured against the memory limit when one is set, or against host RAM when no limit is set (so an unlimited service's "5%" means 5% of host RAM, not of an allocation).

PATCH /api/external-services/{id}/resources — requires ExternalServicesWrite. Persists the new caps to the service's encrypted config and live-applies them via Docker's update API. Memory and CPU can be changed on a running container without a restart; a stopped container also accepts the update and picks up the caps on next start. The request body is the limits object — pass null (or omit) any field to leave it unlimited; a request where every field is null removes all limits.

The response includes a per-member applied[] list so you can tell which containers got the change. Each entry reports an outcome:

OutcomeMeaning
appliedDocker accepted the update; the caps are live now
stoppedContainer exists but isn't running; caps stored and applied on next start
missingContainer does not exist yet; caps stored and applied on first start
faileddocker update returned an error (e.g. the new memory cap is below current usage)

The dashboard surfaces this as an apply-summary toast and rebases its CPU meter against the cap when one is set, so the bar reads as a fraction of the cap instead of a fraction of host cores.


Security Best Practices

  • Name
    Connection Security
    Description
    • Always use SSL/TLS connections (sslmode=require for PostgreSQL)
    • Store credentials in environment variables, never in code
    • Use the Temps-provided connection strings (automatically secured)
  • Name
    Network Access
    Description
    • Services are only accessible from projects within your Temps instance
    • Configure IP allowlists for external access if needed
    • Use private networking for service-to-service communication
  • Name
    Credential Rotation
    Description

    Rotate credentials from the service settings page in the dashboard (Services → select service → Rotate Credentials). Connection strings are automatically updated in all linked projects after rotation.

  • Name
    Audit Logging
    Description

    All service access is logged for security auditing:

    • Connection attempts (successful/failed)
    • Query execution (optional for PostgreSQL)
    • Object access (S3 operations)
    • Configuration changes

Data Safety

Because managed services run on your own infrastructure, you own and control all data at every layer. This section covers how Temps protects your data against loss, corruption, and unauthorized access.

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. If you need encryption at rest for the data itself, enable it at the infrastructure level — your cloud provider's encrypted volumes or LUKS on bare metal. Temps does not manage disk encryption.

In transit, all connections require SSL/TLS (sslmode=require for PostgreSQL). Temps never exposes service ports directly to the public internet.

Data Durability

  • Name
    PostgreSQL
    Description

    New services start with WAL (Write-Ahead Log) archiving off — enable it (which configures a WAL-G destination) to get point-in-time recovery (PITR) back to the oldest retained WAL segment. Automatic daily backups are retained for 7 days (configurable up to 90 days). A confirmed write to PostgreSQL is durable — data is fsynced before the client receives acknowledgment. Read replicas, when configured, maintain additional copies. A built-in WAL health probe watches for WAL bloat and misconfigured archiving so a stalled archive destination can't silently fill your disk.

  • Name
    Redis
    Description

    By default Redis is in-memory only — data does not survive a restart. For caching workloads that can tolerate data loss this is fine. For anything that must survive restarts (sessions, queues), enable RDB snapshots (periodic point-in-time dumps) or AOF persistence (append-only log of every write). If data loss is not acceptable under any circumstance, use PostgreSQL instead.

  • Name
    S3 Object Storage
    Description

    Objects are stored on the underlying server disk. Enable versioning to protect against accidental overwrites and deletes. For disaster recovery, replicate buckets to a separate off-server S3-compatible destination using rclone or a lifecycle replication rule — data on a single disk is not protected against hardware failure.

Data Isolation

Each managed service runs in its own container with a dedicated volume:

  • No cross-service filesystem access — services cannot read each other's data at the OS level
  • Per-project database users — when you link a service to a project, Temps creates a scoped database user with the minimum required privileges for that project; it does not reuse the superuser credential
  • Network isolation — services are only reachable from containers on the same Temps internal network and are not exposed to the public internet unless you explicitly open a port

Deleting a Service

Before deleting, always download a final backup and unlink all projects first:

  1. Download a backup — go to Services → select the service → Backups → download the latest backup.
  2. Unlink all projects — in the service settings, remove all project links so no running app still holds a reference to the credentials.
  3. Delete the service — from the service settings page, click Delete Service and confirm.

Data Ownership

Your managed service data never leaves your server. Temps (the company) has no access to your database contents, Redis data, or S3 objects. See Data Ownership & Privacy for the complete policy including GDPR considerations.


Pricing & Resource Limits

Recommended Resource Allocation:

Use CasePostgreSQLRedisS3 Storage
Small Project1GB RAM, 10GB storage256MB10GB
Medium Project2GB RAM, 50GB storage1GB100GB
Large Project4GB+ RAM, 200GB+ storage4GB+500GB+

Comparison with Other Platforms

FeatureTempsVercelNetlifyRailway
PostgreSQL✅ Built-in⚠️ Vercel Postgres (extra)❌ External only✅ Built-in
Redis✅ Built-in⚠️ Vercel KV (extra)❌ External only✅ Built-in
S3 Storage✅ MinIO-compatible⚠️ Vercel Blob (extra)❌ External only❌ External only
Connection Limits❌ No limits✅ Metered/capped✅ Metered/capped⚠️ Plan-based
Storage Cost❌ Your infrastructure✅ $0.15/GB/month✅ $0.10/GB/month✅ Pay per GB
Backups✅ Automatic⚠️ Enterprise only⚠️ Add-on✅ Automatic

Next Steps

Was this page helpful?