External databases
An external database is one Levelrail connects to but does not run. Use it when a database already holds real data and you want apps to use it where it is: a Postgres container left over from another platform, a managed cloud database, or a server on your LAN. Nothing is copied, restarted or modified. You can move the data into a managed database later, or never.
What it does and does not do
- Stores the address, user, database name, TLS mode and Docker network. The password goes into the same envelope-encrypted secrets store as every other credential and is never returned by the API.
- Shows the database in the Databases list with an External badge. Actions that only make sense for a database Levelrail runs (stop, start, resources, version change, major upgrade, backups, public access) are not offered.
- Injects the same connection env into apps as a managed database does (
host,port,username,password,database,url). - Opens the database viewer against it (Postgres, MySQL, MariaDB), read-only unless an admin uses the guarded write route.
- Removing it deletes only Levelrail's record and the stored password. The database is never contacted.
Connect an existing database
Dashboard: Databases, Connect existing. Pick "A container on this server" or "Enter the connection details", fill in the form, run Test connection, then save.
CLI:
printf '%s\n' "$PGPASSWORD" | levelrail-cli databases connect \
--name legacy --engine postgres --host 10.0.0.5 --user app \
--database appdb --tls-mode require --password-stdin --json
levelrail-cli databases connect --engine postgres --host 10.0.0.5 --test --password-stdinDefaults per engine: port 5432, 3306, 27017 or 6379; TLS "if available" for Postgres, MySQL and MariaDB, off for MongoDB and Redis.
Adopt a running container
levelrail-cli databases adopt --list
levelrail-cli databases adopt --container coolify-pg --name legacy \
--user postgres --database appdb --password-stdinAdopting lists running containers whose image is Postgres (including pgvector, PostGIS, TimescaleDB), MySQL, MariaDB, MongoDB or Redis. Containers Levelrail created itself are never offered. The host, port, Docker network and default user are prefilled. The password is never read from the container: you supply it.
Adopt never stops, restarts, recreates or modifies the container, and never attaches it to another network. Connections are made by container name over the container's own Docker network, because a helper on the default bridge cannot reach a container on another user-defined network through the host's address. The network is stored with the record and re-resolved on every check, so the record follows the container when it is recreated.
Health
A read-only probe runs from a short-lived helper container every five minutes (APP_EXTERNAL_DB_PROBE_INTERVAL, 0 disables) and on demand (levelrail-cli databases probe NAME, or Check now). The helper runs one SELECT style identity query and is removed straight away.
| Status | Meaning | What to do |
|---|---|---|
| reachable | The query ran. | Nothing. |
| slow | Answered, but slower than APP_EXTERNAL_DB_SLOW_MS (1500). | Check load and the network path. |
| auth_failed | User or password refused, or the host is not allowed. | Check them and the server's access rules (pg_hba.conf). |
| tls_error | TLS does not match what the server expects. | Switch TLS between off, if available and required. |
| unreachable | Nothing answered. | Check host, port, network and that it is running. |
Using it from an app
services:
web:
env:
DATABASE_URL: { from: legacy.url }or levelrail-cli apps connect web legacy --field url. The app container is attached to the database's Docker network when it is created and on every reconcile; the database container is not changed. Env values resolve when the container is created, so restart an app that was already running.
Safety rules
- Hosts that are link-local or cloud metadata addresses (169.254.0.0/16, fe80::/10, 100.100.100.200, metadata names) are rejected, including when a hostname resolves to one. Set
APP_EXTERNAL_DB_ALLOW_LINK_LOCAL=trueto opt in. Loopback andlocalhostare always rejected: inside a helper they point at the helper itself. - Creating, testing, adopting and revealing a password require an admin token. Revealing is recorded in the audit log. Listing needs only read.
- The control plane opens no firewall port for an external database.
- The only statements sent to it are the probe query and what an operator runs through the viewer. Writes go through the same admin-only route that requires the database name as confirmation.
- The MCP server exposes
list_external_databases(read-only). No MCP tool can create, change or probe one.
Not covered yet
- Scheduled backups of an external database. The backup pipeline dumps by running the engine's tool inside the database's own container; reusing it for a remote host needs a dump source that is not tied to a managed container.
- The key browser and SQL console for MongoDB and Redis.
- Adopting from a remote agent node lists containers but not their Docker networks, so enter the network by hand there.