PostgreSQL

PostgreSQL databases on the VPS with their own least-privilege user, TLS and external access limited by an IP allowlist.

  • Category: Servers and infrastructure
  • Status: Available
  • Requirements: An active VPS with the postgresql feature available; Being a member with access to the VPS to install; admin to reveal the credential, 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 PostgreSQL database is an app: it has its name, its own user with a random password and, if requested, internet access with TLS and an IP allowlist. The engine is installed once per VPS and shared by all its databases.

  • Create several databases on the same engine, each isolated from the others.
  • See the credential (user, password and connection URI) when needed.
  • Open, change or close internet access by IP.
  • Browse tables and data from the database manager.
  • From the VPS PostgreSQL tab: list and create databases, users and permissions.

When should you use it?

  • An application deployed on the same VPS that needs a relational database (it connects through 127.0.0.1:5432).
  • An external service (BI, another cloud, a workstation) that must read the database with TLS from known IPs.

How does it work in ArduMaker?

  1. Validate: active VPS and the postgresql system feature.
  2. Engine: if not installed, it is installed once (distribution packages postgresql and postgresql-contrib, random admin password stored encrypted). If it was already there, it is not reinstalled and neither its version nor its password changes.
  3. Credential: an app<id>_<hex> user is generated with a random password, encrypted on the platform before touching the VPS.
  4. Role and database: the role is created LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION; the database is created owned by that role, everything is revoked from PUBLIC and a hostssl <db> <role> … scram-sha-256 line is added to pg_hba.conf, with a reject below it for any other database.
  5. Verification: the role cannot be superuser nor create databases or roles, it owns its database and PUBLIC cannot connect. If anything does not match, it fails with permisos_no_verificados.
  6. External access: only if requested with IPs. Engine TLS is enabled and the opening is requested as a separate operation; if it cannot open yet, the database stays installed anyway and the reason is recorded.

SQL travels encoded in the environment and enters through stdin; the password never appears on a command line or in a log.

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
Enginepostgresql service enabled, listening on 127.0.0.1:5432
DatabaseName [A-Za-z_][A-Za-z0-9_]{0,62}, uppercase kept as is (quoted)
App roleapp<id>_<hex>
pg_hba.confIts hostssl block and a reject for every other database
TLS (if external access)Own certificate in /etc/ardumaker/tls/, ssl=on, TLS 1.2 minimum, reload without restart

Internet access and IP allowlist

By default the engine listens only inside the VPS (5432 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 5432 and the firewall lets in only the IPs on the list.

The certificate belongs to that exact name (not a wildcard) and is installed in the engine: PostgreSQL turns on ssl with TLS 1.2 minimum and reloads its configuration without restarting. The app user only gets in over TCP with TLS (hostssl) and only to its database.

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 PostgreSQL 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): DROP DATABASE and, if Apps created the user (app<id>_<hex>), also the role and its stored password. Never a customer user or the engine administrator.
  • 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:5432
  2. External client: db-<id>:<port> with sslmode=verify-full
  3. Host firewall: only the listed IPs
  4. Host proxy: port 61000–64999 → VPS 5432
  5. PostgreSQL: hostssl + scram-sha-256, its database only
Connections to a PostgreSQL database

Expected result

The database shows in Apps hanging from its engine. ver_acceso_app shows state, host, port, TLS and the applied list; revelar_credencial_app returns user, password and the URI. The URI only exists once external access is ready:

postgresql://app<id>_<hex>@db-<vps>.<zona>:<puerto>/<base>?sslmode=verify-full

Errors and troubleshooting

ErrorCauseRetry?
base_ya_existeA database with that name already exists with another owner; it is not overwritten or adoptedNo
motor_detenido / motor_instalandose / motor_no_instaladoThe engine is stopped, installing or could not be installedYes
base_no_creada / permisos_no_verificadosThe SQL failed or the role permissions did not verifyYes
lista_ip_invalida / lista_ip_demasiado_ampliaMalformed entry or range wider than the minimum; data.invalid says whichAfter fixing the list
credencial_legacy / cuentas_del_motor_sin_verificar / tls_no_disponibleThe engine does not yet meet the conditions to be exposed (old passwords, customer accounts not verified or TLS pending)After resolving it
acceso_externo_no_habilitadoExternal access is not enabled for that type in the companyNo
cierre_no_verificadoOn closing, a rule or the listener remained; what is really there is reportedYes

State and steps of each operation: ver_operacion_app (including whether it is retryable). A failed retryable operation resumes from the failed step with reintentar_operacion_app, without reinstalling the engine. Asking for the list already applied queues nothing (idempotent: true).

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: postgres (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