Skip to Content
TestingManaged Databases

Managed Databases

Enable any app to get a one-click managed Postgres database with automatic environment injection, eliminating manual database setup and credential management.

Status

M1 ships in mixed mode: the REST API and MCP surface are final, but provisioning connects to a synthetic host (stub-shared-pg-01.local) instead of a real Postgres VM. In M1.5, when the shared host (of-live-system00-shared-pg-01) becomes operational, the provisioning swaps in real psql via libvirt guest-agent execution.

Overview

Managed databases are stored in data/app-infra/databases.json with append-only semantics. The password is returned exactly once from the provision call and never stored at rest. The MCP tool automatically publishes DATABASE_URL into the app’s environment via the App Secrets producer API, ensuring the password never reaches the caller.

End-to-End Flow

  1. Operator calls add_app_database(app_id='app-abc123', engine='postgres') via MCP.
  2. The MCP tool verifies app ownership, then POSTs to the libvirt REST API /api/app-infra/databases with the app details.
  3. Libvirt provisions the database (stub mode: generates synthetic connection; M1.5: real psql on shared host) and returns db_id plus password once.
  4. The MCP tool immediately publishes DATABASE_URL into the app’s env via the feature-04 producer API (Bearer token), ensuring the password never reaches the caller.
  5. The MCP tool appends db_id to App.db_refs and returns {db_id, status, env_keys, env_published, session_token, next}.
  6. On next deploy_app call, the app VM receives DATABASE_URL in /etc/openfactory/app.env automatically; the app connects without any manual credential setup.

MCP Tools

ToolPurposeExample Call
add_app_database

Provision a managed Postgres database for an app and automatically publish DATABASE_URL into the app’s environment.

add_app_database(app_id=‘app-12345678’, engine=‘postgres’)
list_app_databases

List all databases provisioned for an app (metadata only, no passwords).

list_app_databases(app_id=‘app-12345678’)

add_app_database

Provision a managed Postgres database for an app and automatically publish DATABASE_URL into the app’s environment.

Example Call:

add_app_database(app_id='app-12345678', engine='postgres')

Example Response (when env_published=true):

{ "db_id": "db-a1b2c3d4e5f6", "status": "stub", "engine": "postgres", "env_keys": ["DATABASE_URL"], "env_published": true, "session_token": "user@example.com", "next": "deploy_app(app_id='app-12345678', vm_name=...) — DATABASE_URL is already in the app's env and will be injected on the next deploy." }

Example Response (when env_published=false):

{ "db_id": "db-a1b2c3d4e5f6", "status": "stub", "engine": "postgres", "env_keys": [], "env_published": false, "connection_url": "postgres://app_myapp:password123@stub-shared-pg-01.local:5432/app_myapp", "publish_skipped_reason": "OPENFACTORY_DB_PRODUCER_TOKEN is not configured", "session_token": "user@example.com", "next": "set_app_env(app_id='app-12345678', set={'DATABASE_URL': <connection_url>}) then deploy_app(...)" }

list_app_databases

List all databases provisioned for an app (metadata only, no passwords).

Example Call:

list_app_databases(app_id='app-12345678')

Example Response:

{ "databases": [ { "db_id": "db-a1b2c3d4e5f6", "app_id": "app-12345678", "engine": "postgres", "shape": "shared-tenant", "shared_host": "stub-shared-pg-01.local", "database": "app_myapp", "username": "app_myapp", "host": "stub-shared-pg-01.local", "port": 5432, "status": "stub", "notes": "STUB MODE — no real Postgres provisioned. Set SHARED_POSTGRES_SUPERUSER_PASSWORD + stand up the shared host for real provisioning.", "created_at": "2026-06-26T01:52:00Z" } ], "session_token": "user@example.com" }

MCP Response Fields:

  • session_token: Appended by the MCP tool wrapper (not present in raw REST responses)
  • All other fields come directly from the REST API

REST API

MethodPathPurposeAuth
POST/api/app-infra/databasesProvision a new managed Postgres database for an app. Returns the connection details (host, port, database, username, password, url) exactly once; password never stored.JWT (authenticated user; ownership scoping is applied at the elster proxy layer)
GET/api/app-infra/databases/{db_id}Fetch metadata for a specific database (no password). Returns database configuration, host, port, status, and creation timestamp.JWT (authenticated user)
GET/api/app-infra/databases?app_id={app_id}List all databases for an app with optional filtering by app_id. Returns metadata only (no passwords).JWT (authenticated user)

Provision Endpoint

Request (POST /api/app-infra/databases):

{ "app_id": "app-12345678", "owner_user_id": "user@example.com", "slug": "my-app", "engine": "postgres" }

Response (201):

{ "db_id": "db-a1b2c3d4e5f6", "status": "stub", "connection": { "host": "stub-shared-pg-01.local", "port": 5432, "database": "app_myapp", "username": "app_myapp", "password": "actual_password_here", "url": "postgres://app_myapp:actual_password_here@stub-shared-pg-01.local:5432/app_myapp" } }

The password is returned exactly once and must be published to the app’s environment immediately. Subsequent GET requests never return the password.

Get Database Metadata

Response (GET /api/app-infra/databases/{db_id}):

{ "db_id": "db-a1b2c3d4e5f6", "app_id": "app-12345678", "engine": "postgres", "shape": "shared-tenant", "shared_host": "stub-shared-pg-01.local", "database": "app_myapp", "username": "app_myapp", "host": "stub-shared-pg-01.local", "port": 5432, "status": "stub", "notes": "STUB MODE — no real Postgres provisioned.", "created_at": "2026-06-26T01:52:00Z" }

List Databases

Response (GET /api/app-infra/databases?app_id=app-12345678):

{ "databases": [ { "db_id": "db-a1b2c3d4e5f6", "app_id": "app-12345678", "engine": "postgres", "shape": "shared-tenant", "shared_host": "stub-shared-pg-01.local", "database": "app_myapp", "username": "app_myapp", "host": "stub-shared-pg-01.local", "port": 5432, "status": "stub", "notes": "STUB MODE — no real Postgres provisioned.", "created_at": "2026-06-26T01:52:00Z" } ] }
Last updated on