Set Up Managed Services

Managed services are databases and storage systems that Temps runs as Docker containers on your server: PostgreSQL, MariaDB, Redis, MongoDB and S3-compatible storage. Nothing is provisioned for you automatically. You create a service, link it to a project, and Temps generates credentials, injects them as environment variables on the next deploy, and backs the service up to S3 on your schedule.


Available service types

ServiceDefault ImageUse caseReference
PostgreSQLgotempsh/postgres-walg:18-bookwormPrimary database with WAL-G backups and point-in-time recoveryManaged PostgreSQL
MariaDBmariadb:ltsMySQL-compatible relational database with point-in-time recoveryManaged MariaDB
Redisgotempsh/redis-walg:8-bookwormCaching, sessions, pub/sub, and queuesManaged Redis
MongoDBgotempsh/mongodb-walg:8.0Document databaseManaged MongoDB
S3 Storagerustfs/rustfs:1.0.0-beta.6S3-compatible object storage (powered by RustFS)Managed S3 (RustFS)

MariaDB and RustFS-backed services are opt-in. Installing or starting Temps does not pull or run a MariaDB or RustFS container until you create a service that uses one. Temps itself keeps using PostgreSQL with TimescaleDB for its own database.

Temps also provides higher-level abstractions built on these services:

ServiceBacked byPurpose
KVRedisKey-value storage over an HTTP API and the Temps SDK (KV Storage)
BlobRustFSFile/blob storage accessible via the Temps SDK (SDKs)

PostgreSQL can also run as a pg_auto_failover cluster across several nodes; see Postgres high availability.


Create a service

Create a service

  1. 1

    In the sidebar, click Services.

  2. 2

    Click Create Service.

  3. 3

    Select the service type (PostgreSQL, MariaDB, Redis, MongoDB, or S3 Storage).

  4. 4

    Enter a name and adjust parameters if needed.

  5. 5

    Click Create.

    Checkpoint: Run bunx @temps-sdk/cli services list and confirm the new service appears with status running before linking it.

From the API

curl -X POST "https://your-temps-instance/api/external-services" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-postgres",
    "service_type": "postgres",
    "parameters": {
      "port": "5432",
      "max_connections": "200"
    }
  }'

Configuration by service type

PostgreSQL:

ParameterDefaultEditable after creation
portAuto-assigned from 5432Yes
docker_imagegotempsh/postgres-walg:18-bookwormYes
max_connections100Yes
ssl_modedisableYes
databasepostgresNo (read-only)
usernamepostgresNo (read-only)
passwordAuto-generated (16 chars)No (read-only)

MariaDB:

ParameterDefaultEditable after creation
portAuto-assigned from 3306Yes
docker_imagemariadb:ltsYes
binlog_archive_interval5mYes
size_profilesmallNo (read-only)
databaseappNo (read-only)
usernameappNo (read-only)
passwordAuto-generatedNo (read-only)
root_passwordAuto-generatedNo (read-only)

Redis:

ParameterDefaultEditable after creation
portAuto-assigned from 6379Yes
docker_imagegotempsh/redis-walg:8-bookwormYes
passwordAuto-generatedNo (read-only)

MongoDB:

ParameterDefaultEditable after creation
portAuto-assigned from 27017Yes
docker_imagegotempsh/mongodb-walg:8.0Yes
databaseadminNo (read-only)
usernameadminNo (read-only)
passwordAuto-generatedNo (read-only)

S3 / RustFS:

ParameterDefaultEditable after creation
portAuto-assigned from 9000Yes
console_portAuto-assigned from 9001Yes
docker_imagerustfs/rustfs:1.0.0-beta.6Yes
access_keyAuto-generatedNo (read-only)
secret_keyAuto-generatedNo (read-only)

All ports are bound to 127.0.0.1 only — they are not publicly accessible from outside the server. Application containers communicate with services over the Docker network using container names as hostnames.


Linking injects the service's connection credentials as environment variables into your project's containers at deploy time.

Link a service to a project

  1. 1

    Open your project.

  2. 2

    Click Services in the project sidebar.

  3. 3

    Click Link Service.

  4. 4

    Select the service.

  5. 5

    Click Link.

    Checkpoint: On the next deploy, the connection credentials are injected as environment variables; confirm with bunx @temps-sdk/cli services env --id 12 -p my-app.

From the API

curl -X POST "https://your-temps-instance/api/external-services/{service_id}/projects" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": 1
  }'
  1. Per-project isolation — For PostgreSQL and MariaDB, a dedicated database is created named {project_slug}_{environment_slug}. For Redis, a database number (0-15) is assigned. For S3, a dedicated bucket is created.
  2. Environment variables — Connection credentials are generated using the Docker container name as the hostname (for internal networking) and injected on the next deploy.
  3. One per type — Only one service of each type can be linked to a project. To switch to another instance of the same type, unlink the current one first.

Environment variables by service

ServiceVariables injected
PostgreSQLPOSTGRES_URL, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD (no DATABASE_URL)
MariaDBDATABASE_URL, MYSQL_URL, MARIADB_URL (all mysql://), plus MYSQL_* and MARIADB_* host, port, database, user and password
RedisREDIS_URL, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_DATABASE
MongoDBMONGODB_URL, MONGODB_HOST, MONGODB_PORT, MONGODB_DATABASE, MONGODB_USERNAME, MONGODB_PASSWORD
S3S3_ENDPOINT, S3_HOST, S3_PORT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY, S3_REGION, plus the AWS_* equivalents

If your framework expects DATABASE_URL for PostgreSQL, map it yourself, for example by reading POSTGRES_URL in your config.

Preview environment variables

To see what variables will be injected without deploying:

# Preview variable names
curl "https://your-temps-instance/api/external-services/{service_id}/preview-environment-names" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Preview with masked values
curl "https://your-temps-instance/api/external-services/{service_id}/preview-environment-masked" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Get actual values for a project
curl "https://your-temps-instance/api/external-services/{service_id}/projects/{project_id}/environment" \
  -H "Authorization: Bearer YOUR_TOKEN"

Unlink a service from a project

  1. 1

    Open your project and click Services in the project sidebar.

  2. 2

    Find the linked service in the list.

  3. 3

    Choose Unlink for that service.

  4. 4

    Confirm the unlink.

    Checkpoint: Unlinking does not delete the data inside the service; you can re-link later. Confirm the project no longer appears under bunx @temps-sdk/cli services projects --id 12.

curl -X DELETE "https://your-temps-instance/api/external-services/{service_id}/projects/{project_id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Unlinking does not delete the database or data inside the service — it only stops injecting environment variables. You can re-link later.


Import an existing container

If you already have a PostgreSQL, MariaDB (or MySQL-compatible), Redis, MongoDB or S3-compatible container running on your server, you can import it into Temps instead of creating a new one.

Import an existing container

  1. 1

    Go to Services.

  2. 2

    Click Import Container.

  3. 3

    Temps scans for running Docker containers that match known service types.

  4. 4

    Select the container to import.

  5. 5

    Provide the connection credentials (Temps cannot read them from the container).

    Checkpoint: Run bunx @temps-sdk/cli services list and confirm the imported service is listed.

From the API:

# List importable containers
curl "https://your-temps-instance/api/external-services/available-containers" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Import one
curl -X POST "https://your-temps-instance/api/external-services/import" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "container_id": "abc123...",
    "name": "existing-postgres",
    "service_type": "postgres",
    "parameters": {
      "host": "localhost",
      "port": "5432",
      "database": "postgres",
      "username": "postgres",
      "password": "your-existing-password"
    }
  }'

Manage service lifecycle

Start and stop

Services can be started and stopped independently:

Start or stop a service

  1. 1

    Open the service from the Services list.

  2. 2

    Use the Stop control to take it offline (the Docker volume and data persist).

  3. 3

    Use Start to bring it back online.

  4. 4

    Applications linked to a stopped service fail to connect until it is started again.

    Checkpoint: Run bunx @temps-sdk/cli services show --id 12 and confirm the status reads running after starting.

# Stop a service (keeps data)
curl -X POST "https://your-temps-instance/api/external-services/{id}/stop" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Start it again
curl -X POST "https://your-temps-instance/api/external-services/{id}/start" \
  -H "Authorization: Bearer YOUR_TOKEN"

Stopping a service does not delete any data — the Docker volume persists. Applications linked to a stopped service will fail to connect until the service is started again.

Health check

curl "https://your-temps-instance/api/external-services/{id}/health" \
  -H "Authorization: Bearer YOUR_TOKEN"

Delete a service

Delete a service

  1. 1

    Always back up important data before deleting a service.

  2. 2

    Open the service from the Services list.

  3. 3

    Choose Delete for the service.

  4. 4

    Confirm deletion.

    Checkpoint: Deleting removes the container and its data volume; confirm the service no longer appears in bunx @temps-sdk/cli services list.

curl -X DELETE "https://your-temps-instance/api/external-services/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Warning: Deleting a service removes its container, its data volume and its backup records. Backup files already uploaded stay in your S3 bucket and can be recovered with an orphan restore. Back up important data before deleting a service.


Upgrade a service

Upgrade a service

  1. 1

    Always back up the service before upgrading.

  2. 2

    Go to the service details page.

  3. 3

    Click Upgrade.

  4. 4

    Specify the target Docker image (e.g. gotempsh/postgres-walg:18-bookworm).

  5. 5

    Confirm the upgrade. This swaps the image within the same major version; for a PostgreSQL major version use Major Version Upgrade instead.

    Checkpoint: Run bunx @temps-sdk/cli services show --id 12 and confirm the version reflects the new image.

services upgrade (and POST /api/external-services/{id}/upgrade) swaps the Docker image within the same major version, for example from postgres:18 to gotempsh/postgres-walg:18-bookworm. A PostgreSQL major-version change through this endpoint is rejected without touching the service.

curl -X POST "https://your-temps-instance/api/external-services/{id}/upgrade" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "docker_image": "gotempsh/postgres-walg:18-bookworm"
  }'

PostgreSQL major version upgrades

For a major version (e.g. 17 to 18), open the service and choose Major Version Upgrade… from its actions menu, or call the upgrades API:

curl -X POST "https://your-temps-instance/api/external-services/{id}/upgrades" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from_version": "17",
    "to_version": "18",
    "from_image": "gotempsh/postgres-walg:17-bookworm",
    "to_image": "gotempsh/postgres-walg:18-bookworm"
  }'

A major upgrade needs a default S3 backup source (the request fails with 412 without one), because Temps takes a backup before it starts. It then runs a resumable sequence: keep the old data volume, dump the old server, start a container on the new version, restore the dump, swap the containers and run ANALYZE. A failed step can be retried from where it stopped, and the pre-upgrade volume is kept for 7 days so you can roll back. Only one major upgrade per service can run at a time (409 otherwise).


PostgreSQL HA clusters

A PostgreSQL service can run as a high-availability cluster instead of a single standalone container. Creating a cluster, how apps connect and its limits are covered in Postgres high availability; this section documents how cluster backups and restores work internally.

Cluster services have topology = "cluster" and service_type = "postgres", and run on the gotempsh/postgres-ha:18-bookworm-walg image. A cluster is made up of a pg_auto_failover monitor plus two or more data members tracked in the service_members table. Linked apps receive a multi-host POSTGRES_URL that lists every data member with target_session_attrs=read-write, which lands writes on the current primary. Connecting through <cluster>.temps.local names instead requires cluster DNS, which is experimental and off by default (details).

Cluster backups and restore use WAL-G physical backups (basebackup plus continuous WAL archiving). Standalone PostgreSQL services also use WAL-G when their image includes it (the default image does), and fall back to pg_dump otherwise; see Backups.

Cluster backup

Back up a cluster

  1. 1

    Open the cluster service and click its Backup button (or use the standard backup API).

  2. 2

    Temps finds the current writable primary via pg_auto_failover.

  3. 3

    It writes a walg.env credentials file to every running data member so archiving survives a failover.

  4. 4

    It runs wal-g backup-push against the primary to upload the basebackup to S3.

  5. 5

    It enables continuous WAL archiving on the primary.

    Checkpoint: WAL archiving does not start until the next restart; confirm the basebackup landed with bunx @temps-sdk/cli services list-backups --s3-source-id 3.

When you back up a cluster (from the service's Backup button or the standard backup API), Temps routes through ExternalServiceManager::backup_postgres_cluster instead of the standalone path. That method:

  1. Finds the current writable primary by asking pg_auto_failover for the member whose live state is primary or single.

  2. Writes a /var/lib/postgresql/walg.env credentials file to every running data member (not the monitor). Writing it everywhere means archiving keeps working after a failover promotes a different member to primary.

  3. Runs wal-g backup-push /var/lib/postgresql/pgdata against the primary to upload the basebackup to S3. WAL segments are lz4-compressed by default.

  4. Enables continuous WAL archiving by running, against the primary:

    ALTER SYSTEM SET archive_command = '. /var/lib/postgresql/walg.env && wal-g wal-push %p';
    ALTER SYSTEM SET archive_mode = 'on';
    ALTER SYSTEM SET wal_level = 'replica';
    SELECT pg_reload_conf();
    

Because archive_command is stored in postgresql.auto.conf — which is part of pgdata and is streamed to replicas — the setting survives a failover. A new primary inherits it without an operator re-running anything, so WAL keeps flowing to the same S3 prefix.

WAL archiving doesn't start until the next restart. archive_mode is a postmaster setting that requires a server restart to take effect. Until the primary restarts — either operator-triggered or pg_auto_failover-driven — archive_command is set but the archiver is not yet enabled, so WAL accumulates locally without being shipped to S3. The basebackup itself is already safely on S3; only continuous WAL shipping waits on the restart.

In-place cluster restore

Restore a cluster in place

  1. 1

    Confirm every cluster member lives on the control plane (in-place restore is single-host only in this MVP).

  2. 2

    Phase 1 Teardown: Temps removes every member, deleting DNS records, containers, and data volumes, while keeping the parent service row so the URL and credentials survive.

  3. 3

    Phase 2 Pre-seed primary: a one-shot helper container runs wal-g backup-fetch and writes recovery.signal plus a restore_command.

  4. 4

    Phase 3 Rebuild: initialize_cluster re-runs with the original member specs, PostgreSQL recovers from WAL, and replicas re-basebackup from the new primary.

    Checkpoint: Run bunx @temps-sdk/cli services restore-runs --id 12 and confirm the run status is completed; application connections reconnect transparently once the cluster is back up.

An in-place restore of a cluster backup routes through ExternalServiceManager::restore_postgres_cluster. It first snapshots the existing member topology (names, ordinals, roles, node assignments), then runs in three phases:

PhaseWhat it does
1. TeardownRemoves every member: deletes DNS records, force-removes each container, removes each {container}_data volume, drops the role/VIP DNS records, and deletes the service_members rows. The parent external_services row is kept on purpose, so the service URL, credentials, and UI bookmarks survive.
2. Pre-seed primaryRuns a one-shot helper container (same gotempsh/postgres-ha:18-bookworm-walg image, bind-mounting the primary's volume) that runs wal-g backup-fetch "$PGDATA" LATEST, writes recovery.signal, and writes a postgresql.auto.conf with restore_command = '. /var/lib/postgresql/walg-restore.env && wal-g wal-fetch %f %p'.
3. RebuildRe-runs initialize_cluster with the original member specs. PostgreSQL recovers from WAL, pg_auto_failover rebuilds the cluster, and replicas re-basebackup from the new primary.

Because container names derive deterministically from ordinal (postgres-<cluster>-<ordinal>), the rebuilt cluster keeps the same names, ordinals, and DNS records, so the injected connection string still works. Application connections reconnect once the cluster is back up.

Single-host only in this MVP. If any cluster member has a node_id (i.e. it lives on a remote worker), an in-place restore is refused with: MVP: cluster restore is single-host only (no remote members yet). Move all members to the control plane or wait for the multi-host restore path. Move every member to the control plane first. This restriction applies only to restore — cluster backup has no single-host limitation. Restore is always to the latest backup; the point-in-time recovery_target argument is reserved but currently ignored.


Next steps

  • Backups — schedule backups of every service to an S3 bucket
  • Restore & Recovery — restore in place, into a new service, or to a point in time
  • Deploy with a database — tutorial: create a database, link it and deploy an app that uses it

Last updated

Was this page helpful?