feat: external databases — register, probe, encrypted credentials

Databases hosted outside Containr can now be registered alongside
managed ones. The connection is probed before storing (protocol-level
for postgres/redis-family via real drivers, TCP dial for the rest),
credentials are encrypted at rest and never returned, and external rows
skip managed lifecycle actions, backups, and Docker metrics.

- POST /databases/register-external — probe + create
- POST /databases/test-connection — ad-hoc probe (ok/latency_ms/error)
- POST /databases/{id}/test-connection — re-probe stored credentials
- provider/external fields on database services; privileged reads get a
  composed connection URL with loopback hosts rewritten to the caller's
- CLI: databases register + test-connection; MCP: 3 new tools
- UI: managed/external toggle in the create modal, external badge and
  host display, per-row test button, backups section hidden
- Migration widens the plan CHECK to admit 'external'

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-10-08 22:09:42 +02:00
1 parent 3664eaed1a
commit 5dc9b5f115
19 files changed
+1274 -48

No files matched your search

+123
View File
@@ -2050,6 +2050,80 @@ paths:
schema:
$ref: '#/components/schemas/DatabaseCreateResponse'
/databases/register-external:
post:
tags:
- Databases
summary: Register external database
description: Record a database hosted elsewhere — connection is probed first (postgres/redis get protocol-level checks, others TCP). Password stored encrypted, never returned.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/ExternalDatabaseRequest'
- type: object
required: [name]
responses:
'201':
description: External database registered
'400':
description: Validation failed or connection probe failed
'409':
description: Name already in use
/databases/test-connection:
post:
tags:
- Databases
summary: Probe a database connection
description: Ad-hoc connectivity check — postgres via libpq ping, redis/dragonfly via PING, other engines via TCP dial. Always 200; `ok` carries the result.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalDatabaseRequest'
responses:
'200':
description: Probe result
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
latency_ms:
type: integer
error:
type: string
/databases/{id}/test-connection:
post:
tags:
- Databases
summary: Re-probe a registered external database
description: Uses the stored credentials. External databases only.
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Probe result (`ok` carries the outcome)
'404':
description: Database not found
/databases/{id}:
get:
tags:
@@ -6241,6 +6315,12 @@ components:
backup_schedule:
type: string
description: Cron expression for automatic backups, empty when unset
provider:
type: string
enum: [managed, external]
description: managed = Containr-provisioned container; external = registered connection to a database hosted elsewhere
external:
$ref: '#/components/schemas/DatabaseExternal'
next_backup_at:
type: string
format: date-time
@@ -6283,6 +6363,49 @@ components:
type: string
description: Database region
DatabaseExternal:
type: object
properties:
host:
type: string
port:
type: integer
database:
type: string
username:
type: string
has_password:
type: boolean
ssl:
type: boolean
ExternalDatabaseRequest:
type: object
required:
- type
- host
properties:
name:
type: string
description: Display name (required for register, unused for probes)
type:
type: string
enum: [postgresql, redis, mysql, mariadb, mongodb, clickhouse, dragonfly]
host:
type: string
port:
type: integer
description: Defaults to the engine's standard port
database:
type: string
username:
type: string
password:
type: string
description: Stored encrypted; never returned
ssl:
type: boolean
UpdateDatabaseRequest:
type: object
properties:
+2
View File
@@ -48,6 +48,8 @@ logs <service-id> [--follow] runtime logs (SSE follow)
exec <service-id> -- <cmd> one-off container command
variables list|set|unset env vars (secrets masked; --redeploy applies them)
databases list|get|create|delete|action|backup|restore|download-backup
databases register <name> register an external database (--type --host --port --database --username --password --ssl; probed before storing)
databases test-connection probe a connection — by id, or ad-hoc flags
cron list|get|create|delete|trigger|executions
templates list|get|create|delete|deploy
nodes list|get|delete|tokens node agents + onboarding tokens (admin)