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
- Operator calls
add_app_database(app_id='app-abc123', engine='postgres')via MCP. - The MCP tool verifies app ownership, then POSTs to the libvirt REST API
/api/app-infra/databaseswith the app details. - Libvirt provisions the database (stub mode: generates synthetic connection; M1.5: real psql on shared host) and returns
db_idplus password once. - The MCP tool immediately publishes
DATABASE_URLinto the app’s env via the feature-04 producer API (Bearer token), ensuring the password never reaches the caller. - The MCP tool appends
db_idtoApp.db_refsand returns{db_id, status, env_keys, env_published, session_token, next}. - On next
deploy_appcall, the app VM receivesDATABASE_URLin/etc/openfactory/app.envautomatically; the app connects without any manual credential setup.
MCP Tools
| Tool | Purpose | Example Call |
|---|---|---|
add_app_database | Provision a managed Postgres database for an app and automatically publish | 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
| Method | Path | Purpose | Auth |
|---|---|---|---|
| POST | /api/app-infra/databases | Provision 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"
}
]
}