MySQL

MySQL 8 or MariaDB databases on the VPS with their own TLS-only account, grants on its database only and IP-based external access.

  • Category: Servers and infrastructure
  • Status: Available
  • Requirements: An active VPS with the mysql feature available; If the engine already exists: MySQL 8.x or MariaDB 10.4 or later; Being a member with access to the VPS to install; admin to reveal, open access or delete data
  • MCP tools (Read only): ver_catalogo_apps, ver_preflight_app, ver_app, ver_acceso_app, ver_operacion_app, listar_motores_db_vps, listar_bases_db_vps
  • MCP tools (Write or modify): instalar_app, revelar_credencial_app, cambiar_lista_ip_app, reintentar_operacion_app
  • MCP tools (Destructive): revocar_acceso_app, desinstalar_app

Markdown

What can you do with it?

Every MySQL database is an app with its own account. The engine (MySQL or MariaDB) is one per VPS and shared by its databases; if it does not exist, MySQL is installed from the distribution packages.

  • Create several utf8mb4 databases, each with its account and grants.
  • Reveal user, password and connection URI.
  • Open the engine to the internet only for certain IPs, with TLS.
  • From the VPS MySQL tab: databases, users, grants and tables.

When should you use it?

  • Applications that expect MySQL (PHP, Laravel, many third-party tools).
  • A WordPress site uses this same engine, with its own database and account per site.

How does it work in ArduMaker?

  1. Engine: if the agent reports it is not installed, it is installed once (mysql-server and mysql-client); if it exists, it is never reinstalled nor upgraded.
  2. Variant: mysql --version is read. Only MySQL 8.x or MariaDB 10.4+; anything else fails with motor_no_soportado without running SQL.
  3. app<id>_<hex> credential with a random password, stored encrypted before touching the VPS.
  4. It logs in as administrator through the socket, with the password in an ephemeral options file, never on the command line.
  5. Account 'app<id>_<hex>'@'127.0.0.1' with REQUIRE SSL, and GRANT ALL PRIVILEGES only on <db>.*, without GRANT OPTION, before creating the database.
  6. CREATE DATABASE <db> CHARACTER SET utf8mb4 only if it did not exist.
  7. Verification: TLS required, no global privileges and grants on a single database, its own.

The account is @'127.0.0.1' on purpose: not % (it would open the account to any origin) nor localhost (socket, no TLS). When the server lacks partial_revokes (or is MariaDB), the _ in the name is escaped in the GRANT, because there it is a wildcard.

config.external_access decides whether the database is reachable from the internet: {"ips": [...]} opens it only to those IPs; {"local": true} (or false) keeps it inside the VPS. If omitted, the dashboard uses the installer's IP; over MCP the agent's IP does not count, so without ips the database stays local instead of failing.

What actually changes?

WhatDetail
EngineMySQL or MariaDB listening on 127.0.0.1:3306
Databaseutf8mb4; the same name with different case counts as the same
Account'app<id>_<hex>'@'127.0.0.1' REQUIRE SSL
TLS on MySQL 8SET PERSIST + ALTER INSTANCE RELOAD TLS, no restart
TLS on MariaDB90-ardumaker-tls.cnf file; the first time requires restarting the engine

Internet access and IP allowlist

By default the engine listens only inside the VPS (3306 on 127.0.0.1). To use the database from outside, ArduMaker publishes an endpoint of the engine: db-<VPS id> inside the platform zone (direct DNS record, no proxy), on a reserved port between 61000 and 64999. On the host, a proxy carries that port to the VPS 3306 and the firewall lets in only the IPs on the list.

The certificate belongs to the db-<id> name and mysqld terminates it: the host proxy only passes TCP. The app account requires SSL wherever it comes from.

List ruleDetail
FormatIPv4 or IPv6, with or without prefix; a single IP is /32 or /128. No names, loopback, link-local or multicast
WidthMinimum prefix /24 for IPv4 and /48 for IPv6 by default; 0.0.0.0/0 and ::/0 are rejected in the list
SizeUp to 20 entries; empty = closed to everyone
Host namesUp to 5 (allowed_hosts), with up to 8 public IPs each; re-resolved every 5 minutes, and a DNS failure keeps the last known value
Open to everyoneOnly with allow_any and confirm_open together; the answer says so in open_to_any

The list belongs to the engine, not to each database: changing it from any MySQL database changes access for every database of that engine on that VPS (the answer says so with shared_with_engine and affected_apps).

  1. Missing firewall rules are added, one per CIDR (ufw allow from <cidr> to any port <port> proto tcp).
  2. Then the extra ones are removed and their open connections are cut.
  3. Only then is the listener created and verified: rules equal to the list, no rule open to anyone, listener present and a TLS handshake with the right name.

Uninstalling, persistence and backups

  • Without delete_data (the default): the app's own external access is closed and it stops being listed as installed, but the database, its user and its data stay on the VPS. The next reconciliation shows it as discovered. Nothing is lost.
  • With delete_data: true (confirmed twice, admins only): the database is dropped and, if Apps created the account, the account and its stored password too. Never a customer account nor root.
  • The engine is never uninstalled from Apps: it stays installed even with no databases.
  • If the VPS or the engine do not answer, desinstalar_app with forzar=true tries each step once, removes the app and lists in not_cleaned what was left on the server.
  • Data lives on the VPS disk and travels in its weekly backup, which is taken while the VPS is running. For a consistent copy of a specific database it is better to also make your own dump from the database manager (export) or with the engine's tools.

Diagram

  1. App on the same VPS: 127.0.0.1:3306
  2. External client: db-<id>:<port> with TLS identity verification
  3. Host firewall and proxy: listed IPs only → VPS 3306
  4. mysqld: terminates TLS; the account requires SSL and sees only its database
Connections to a MySQL database

Expected result

The URI (without password) appears once external access is ready; the full one is obtained with revelar_credencial_app.

mysql://app<id>_<hex>@db-<vps>.<zona>:<puerto>/<base>?ssl-mode=VERIFY_IDENTITY
mysql://app<id>_<hex>@db-<vps>.<zona>:<puerto>/<base>?sslMode=verify-full   (MariaDB)

Errors and troubleshooting

ErrorCauseRetry?
motor_no_soportadoNot MySQL 8.x nor MariaDB 10.4+; nothing is touchedNo
base_ya_existeA database with that name exists (case-insensitive) on which the account has no grantsNo
motor_detenido / base_no_creada / permisos_no_verificadosEngine stopped, failed SQL or grants that did not verifyYes
requiere_reinicioMariaDB needs a restart for the first TLS; access does not open until resolvedAfter restarting
External access not requestedThe engine has anonymous or passwordless accounts the listener would exposeAfter removing those accounts

The other causes (IP list, TLS, old credentials, unverified closing) are the same as in PostgreSQL. Follow-up is with ver_operacion_app and reintentar_operacion_app.

MCP access

The whole database lifecycle can be operated over MCP with the permissions of the person who authorized the agent. Reference at /mcp/docs and /mcp/bases-de-datos.

  • Read: ver_catalogo_apps, ver_preflight_app, listar_apps_empresa, ver_app, ver_acceso_app (host, port, TLS and IP list, no secrets), ver_operacion_app.
  • Modify: instalar_app with app_type: mysql (requires idempotency_key), cambiar_lista_ip_app (only if its owner is an admin; IPs must be named explicitly: the agent's IP is not the person's), reintentar_operacion_app, refrescar_apps.
  • Sensitive: revelar_credencial_app (admins only, no idempotency_key; the answer is marked sensible: true and the audit records that it was revealed, never the value).
  • Destructive: revocar_acceso_app (closes external access for the whole engine) and desinstalar_app (with delete_data it erases data; requires confirm_delete_data).

To read and write data inside the database, the agent uses the database manager tools. An agent authorized as read-only does not see the tools that modify and cannot reveal credentials.

Related features