A self-service platform that provisions and manages Postgres instances on demand — create/delete/resize, managed roles, credential rotation, automated backups, patching, and TTL cleanup — with team-based multi-tenancy and quotas.
The control plane is a FastAPI service; tenant databases run as Docker containers.
Design docs live in docs/; this README is how to run it.
API (FastAPI) ──writes desired state──▶ Metadata DB (Postgres)
│ ▲
│ Docker SDK │ reconciler + scheduler
▼ │
pg-instance-1 pg-instance-2 … (tenant Postgres containers)
See ARCHITECTURE.md for the control/data-plane split (ADR-001), the desired-state reconciler (ADR-003), and the provisioner abstraction (ADR-002).
| Path | What |
|---|---|
app/api/ |
HTTP routers (thin: validate, authenticate, delegate) |
app/services/ |
use-cases: enforce policy, write desired state, enqueue jobs |
app/lifecycle/ |
workers: job queue, reconciler, backups, reaper, scheduler |
app/provisioner/ |
the only code that talks to Docker (docker_driver.py) |
app/models.py |
metadata schema (source of truth) |
cli/ |
thin HTTP client for demos |
tests/ |
pytest suite; runs Docker-free via a fake provisioner |
# 1. Generate the credential-encryption key (ADR-005)
export MDBAAS_CREDENTIAL_ENCRYPTION_KEY=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
# 2. Start the control plane (API + metadata Postgres)
docker compose up --buildThe API is published on host port 8001 by default (docker-compose.yml). If
that's taken, override it: MDBAAS_HOST_PORT=8010 docker compose up --build.
On first start the API logs a one-time bootstrap admin API key — grab it:
docker compose logs api | grep "bootstrap admin API key"
export MDBAAS_API_KEY=mdb_... # from the log line
export MDBAAS_API_URL=http://localhost:8001Interactive docs (web): http://localhost:8001/docs
curl -s "$MDBAAS_API_URL/health"
curl -s -X POST "$MDBAAS_API_URL/v1/teams" \
-H "Authorization: Bearer $MDBAAS_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"acme"}'
TEAM=<id-from-response>
curl -s -X POST "$MDBAAS_API_URL/v1/instances" \
-H "Authorization: Bearer $MDBAAS_API_KEY" -H "Content-Type: application/json" \
-d "{\"team_id\":\"$TEAM\",\"name\":\"app-db\",\"size\":\"small\"}"
DB=<id-from-response>
curl -s "$MDBAAS_API_URL/v1/instances/$DB/status" -H "Authorization: Bearer $MDBAAS_API_KEY"
curl -s -X POST "$MDBAAS_API_URL/v1/instances/$DB/backups" -d '{}' \
-H "Authorization: Bearer $MDBAAS_API_KEY" -H "Content-Type: application/json"
curl -s -X DELETE "$MDBAAS_API_URL/v1/instances/$DB" -H "Authorization: Bearer $MDBAAS_API_KEY"The CLI (cli/mdbaas.py) is a thinner wrapper over the same endpoints — install it
if you'd rather type mdbaas instances create ... than curl:
python3 -m venv .venv
# cryptography/bcrypt ship as source sdists on some platforms without a matching
# wheel; force prebuilt binaries to avoid needing a Rust/C toolchain.
.venv/bin/pip install --only-binary=:all: cryptography bcrypt
.venv/bin/pip install -e .
source .venv/bin/activatemdbaas health
mdbaas teams create acme
mdbaas instances create app-db --team <team-id> --size small
mdbaas instances status <instance-id> # poll until observed_state == ready
mdbaas backups create <instance-id> # manual backup (pg_dump)
mdbaas instances rotate <instance-id> # rotate superuser credential
mdbaas instances delete <instance-id> # async delete (final backup by default)python -m venv .venv && source .venv/bin/activate
pip install ".[dev]"
pytest # 13 tests, no Docker requiredThe suite uses a file-backed SQLite metadata DB and a FakeProvisioner, so it
exercises the real API, services, job queue, and reconciler without spinning up
containers. MDBAAS_ENABLE_SCHEDULER=false keeps the background scheduler off
during tests.
All settings use the MDBAAS_ prefix — see .env.example. The
important ones: MDBAAS_DATABASE_URL, MDBAAS_CREDENTIAL_ENCRYPTION_KEY,
MDBAAS_PG_IMAGE_DEFAULT, and the MDBAAS_PORT_RANGE_* for the ADR-007 port pool.
This is an MVP. The correctness/security hardening from the
design review that is cheap enough to do now is built
in — atomic port allocation, desired-state reconciliation, SKIP LOCKED job
claiming, advisory-locked schedules, hashed API keys, idempotent create, two-phase
expiry. The documented accepted limitations (soft storage quotas, on-host
backups, port-per-instance, single-host scheduler) and the v1.1/v2 roadmap are in
design-review §6–7.