Databases & services

A database is a container, not a subscription

Backing services run through the exact same primitives as your app: a pinned image, bind-mounted data, a spot on the internal network. Pick an engine and version, bind an app to it, and Sproobo wires the connection string for you.

The engine catalog

Every service is a pinned, choosable version, never an implicit "latest". Today's catalog:

  • PostgreSQL: 14 · 15 · 16 · 17
  • MySQL: 8.0 · 8.4
  • MariaDB: 10.11 · 11.4
  • Redis: 7
  • MongoDB: 7.0 · 8.0. The root user lives in the admin auth database, and a bound app's MONGO_URL always carries ?authSource=admin.
  • Elasticsearch: 8.19.18 · 9.4.3. Runs single-node with security on; a bound app receives an ELASTICSEARCH_URL authenticating the built-in elastic superuser. Provision + connect only today — per-binding users and backups aren't supported yet.

The version list is code-defined and versioned with the control plane, so it evolves: the picker always reflects exactly what's offered today, not this page.

On the roadmap: Elasticsearch now ships (provision + connect only — backups and per-binding users are not supported for it yet). A custom-service escape hatch (bring your own image with a bind-mounted data dir) is still planned and not shipped yet.

Creating a service

From the dashboard's New service flow, or by having an MCP-native client (an AI agent, an editor connector) call the create_service tool over the same audited API. It's plan-first like every other write: a dry run unless the call carries approve: true, and it needs at least a MEMBER role. The CLI's sproobo services command lists what already exists; it doesn't provision a new one:

sproobo services

How a service actually runs

Provisioning compiles a plan from the same primitives as an app deploy, with no separate machinery for databases:

01

Pull the pinned image

The exact engine:version you chose, e.g. postgres:16.

02

Bind-mount the data directory

Under /var/lib/sproobo/services/<id>/data: owner-inspectable, and excluded from any prune or cleanup sweep.

03

Start the container on the internal network

On sproobo-int only, with mandatory memory and CPU limits so one service can't starve the host or its neighbors, and a restart policy that can't race a cleanup sweep.

04

Health probe gates readiness

E.g. pg_isready for Postgres: the service isn't considered up until its probe passes.

The data directory outlives the container.

Because the data directory is a stable bind mount, replacing a service's container means a brief moment of unavailability, never data loss. A version upgrade goes one step further: it restores into a fresh, separately versioned data directory and leaves the previous one intact on the box as a rollback. Stateful containers don't get the side-by-side blue-green swap apps do; the bind mount is what makes the replacement safe.

No published ports, ever

Backing services attach only to the internal sproobo-int Docker network, never -p-published to 0.0.0.0. Apps reach a service by its container DNS name on that same network; there's nothing listening on a public port for anything outside the box to find.

Binding an app to a service

Binding is what turns "a Postgres container exists" into "my app has a DATABASE_URL". From the service's page in the dashboard, bind an app in one click. On the app's next deploy, the service's connection string is injected into its environment automatically (DATABASE_URL for the SQL engines, MONGO_URL for MongoDB, REDIS_URL for Redis, ELASTICSEARCH_URL for Elasticsearch), pointing at the service's DNS name on sproobo-int.

Today that connection string carries the service's admin credentials, the ones generated when the service was created, so the bound app has full access to the service, including creating its own databases and schemas. The wiring is resolved fresh on every deploy, so it's reproducible rather than a one-off manual step, and unbinding removes it the same way on the next deploy.

On the roadmap: per-app auto-provisioning (a dedicated database plus a least-privilege user for each bound app, instead of the shared admin credentials) is designed but not yet wired into the product for production apps. Preview deployments already run on exactly that machinery, see below.

Previews get a scratch database, not your production one

When an app has preview deployments turned on, each pull request's preview provisions its own database plus a least-privilege user scoped to it, inside the same service container, on the preview's first deploy. The scratch credentials are injected over the same env var name the production binding uses, so DATABASE_URL in a preview points at a different database and a different user on the same host, with no code change in your app. Both are dropped when the PR closes.

That preview user is granted rights on its own scratch database only. It is not a superuser, cannot create databases or roles, and holds no table privileges on any other database in the container, so it cannot read or write your production data. Scratch provisioning also revokes the default PUBLIC CONNECT on the container's other databases — existing roles keep their access through explicit grants — so the preview user cannot even open a connection to another database or read catalog metadata such as table names.

This works for the engines that have database-level isolation: postgres, mysql, and mariadb. Redis and Elasticsearch have no such boundary, so a preview binds to the app's production instance for those, unchanged. For a cache that's usually fine; if you keep something in Redis that a preview must not read, write, or evict, treat that as a real caveat today.

Version upgrades are never a bare tag bump

Picking a version at create time is free, but upgrading an existing service is its own gated operation, not a container restart with a new image tag. It requires a verified backup first and runs a dump-and-restore recipe into a fresh, separately versioned instance, so the version you're upgrading from is never touched or overwritten. See Backups & restore for the full mechanics, including why upgrade_service is the one write on the whole platform that can never be pre-approved for automation.

Service caps scale with your plan.

Free keeps 2 services per org, Pro up to 10, and Team and Enterprise are unlimited. See Pricing for the full breakdown.

Next

  • Read the full backup, restore, and upgrade mechanics on Backups & restore.
  • See how a service's injected env reaches your app's build and runtime on Apps & deploys.
  • Read the full trust-boundary and data-ownership model on Security model.