Database servers
A Database server is a MySQL server that Orbit runs and administers. It runs as a Docker Node Process, and the Gateway keeps its root password. Orbit uses that password to create a database for an Instance and to clone the default Instance’s database.database:server lists the commands.
Create a server
database:server:create creates the server and its Process in one command.
The Gateway creates the server in this order:
- It generates a random root password and stores it encrypted on the server record.
- It creates a Docker Node Process named
<slug>with imagemysql:<tag>, the port published on the Node’s WireGuard address to container port3306, and the volumeorbit-<slug>-datafor/var/lib/mysql. - It starts the Process with
MYSQL_ROOT_PASSWORDset and waits until MySQL accepts the root login. - It removes the variable from the Process and recreates the container. The data volume keeps the password.
docker inspect, or a file on the Node. Orbit waits for the root login over TCP inside the container, which only the final MySQL server accepts, so it never recreates the container while MySQL still initializes its data directory.
A retry of the same request continues from the first step that did not finish. A request with the slug of an existing server and another Node, tag, or port returns database.server_slug_conflict. A repeat of a finished request returns the server with status 200.
The response holds id, slug, node_id, process_id, tag, port, status, and databases_count. database:server:show also returns databases, the connections of the databases on the server. No response, Activity entry, or error contains the password.
The server’s Process is an ordinary Docker Node Process: read its logs, stop it, or restart it with the process commands. process:destroy refuses it with process.required_by_database_server; remove the server instead.
Create a database on a server
database:create with --server creates a database and a user on the server, then records the connection.
<project>_<instance> like its databases. A second database for the same Instance reuses that user and its password. That user can reach only the databases the Instance owns and their test databases. A database created without --instance gets its own user, named like the database. The connection gets driver mysql, the server’s Node address and port, and a link to the server.
Orbit refuses a name with database.name_conflict when the server already has that database or user, or when the name starts with the test database name of another database on the server. So one Instance’s user never reaches another database through its test database grant. A create that fails drops what it made and records nothing.
With --instance, Orbit also creates the test database <name>_test, attaches the connection to the Instance under prefix DB, and records the Instance as the owner. Test databases describes how tests use it.
database:create --instance needs an existing Instance, so it comes after that Instance’s setup steps. To have the database before setup runs, pass the server to instance:create --database-server instead.
Add a user
database:user:create adds a user to a database on a server, for example a read-only user in production. database:user:list lists the users that Orbit created for a connection.
SELECT on the database. Any other user gets all privileges on it. Running the command again sets the password and replaces the privileges. A connection without a server returns database.server_required (422). The user of a connection on the same server returns database.name_conflict (409), so the command never changes the password that a connection uses. The Gateway stores the user’s password encrypted, so it can publish it to Vaultwarden. No response returns it.
How Orbit runs admin commands
Every admin command runsmysql or mysqldump inside the server’s container with docker exec, over SSH. The root password travels on protected standard input, and the remote script hands it to docker exec as MYSQL_PWD through the environment. The SQL follows it on standard input. Neither appears as an argument, in a file, or in a log.
Remove a database
database:destroy of a connection on a server drops the database, every database whose name starts with its test database name, and each user Orbit created for it that no other connection on the server uses. Then it deletes the record. A failed drop keeps the record, and a repeat drops again.
Remove a server
database:server:destroy <slug> refuses with database.server_in_use (409) while a connection points to the server. Otherwise it removes the Process and the server record. It keeps the volume orbit-<slug>-data, so the data survives. Remove the volume on the Node by hand when its data must go.
A Node that runs a server cannot be removed. Node removal refuses it with node.has_database_servers, also offline.
Errors
The server commands return these codes.Why it works this way
These reasons explain the design. Check them before you propose a change.The root password lives only in the Gateway
The MySQL image readsMYSQL_ROOT_PASSWORD only when it creates an empty data directory. So Orbit passes it once and then removes it. A password in the Process environment would stay unencrypted in the Gateway and visible through docker inspect on every Node, production included. Encrypting the Process environment was rejected, because the container would still show the password.