External Database
By default, the on-premises control plane bundles its own PostgreSQL database. To use a database managed by your DBA team instead, disable the bundled instance and configure the control plane to connect to your database.
This guide covers the PostgreSQL-backed external database path for the on-premises control plane. The control-plane connection role does not need PostgreSQL superuser privileges. Other database engines are not supported for this deployment path.
The control-plane backend services share one database:
cp-api: Control-plane API server. It runs the schema migration on startup.dp-manager: Data-plane manager. It connects to the same database and runs no migration.
PostgreSQL Requirements
- PostgreSQL 14 or later.
- One empty database owned by the role the control plane connects as.
- One login role with
CREATEROLEandBYPASSRLS, but notSUPERUSER. - No PostgreSQL extensions. UUIDs use the built-in
gen_random_uuid().
Why the Control Plane Needs These Privileges
On its first start, cp-api prepares the database schema and creates an internal low-privilege role. The connection role needs enough privilege for that bootstrap work and for shared control-plane operations.
Each privilege has a specific purpose:
- Database ownership lets
cp-apicreate thepublicandauthschemas, tables, indexes, functions, and Row-Level Security policies. Under default PostgreSQL privileges, the database owner can create objects inpublic, so a standard cluster needs no separate schema grant. CREATEROLEletscp-apicreate and configurecp_api_app, the internal role used for tenant-scoped queries with Row-Level Security. You do not create this role yourself.BYPASSRLSlets shared control-plane operations read rows across organizations when required. These paths include caller-token authentication, billing webhooks, the background budget aggregator, anddp-manageroperations.
Tenant-scoped requests still switch to cp_api_app, which has neither SUPERUSER nor BYPASSRLS. The control-plane connection role therefore needs specific PostgreSQL capabilities, but not superuser privileges.
Create the Database and Role
Run this once as a database administrator. Use an account that can create roles, create databases, and grant BYPASSRLS. The control-plane connection role itself does not need to be a superuser.
-- 1) A dedicated login role for the control plane. Not a superuser.
CREATE ROLE aisix LOGIN PASSWORD 'change-me-to-a-strong-password'
NOSUPERUSER CREATEROLE BYPASSRLS;
-- 2) A dedicated database owned by that role.
CREATE DATABASE aisix_cloud OWNER aisix;
Choose your own role name, password, and database name. Ownership is what lets aisix create objects in public and add the auth schema on first boot.
Use a URL-safe password because the password is embedded in a PostgreSQL connection URL. Characters such as +, /, and = can break the DSN unless they are percent-encoded. Generate a URL-safe value instead, for example with openssl rand -hex 24. See On-Premises Deployment.
If your DBA has hardened the public schema, for example by revoking the default CREATE privilege, also grant schema access explicitly while connected to the new database:
\c aisix_cloud
GRANT USAGE, CREATE ON SCHEMA public TO aisix;
Do not pre-create the cp_api_app role. The control plane creates and configures it during migration and asserts its attributes. Pre-creating it, especially with different attributes, can make the migration fail its safety checks.
Connect the Control Plane to the Database
With Helm, set the externalDatabase.* values (and postgresql.builtin=false) as described in On-Premises Configuration. The chart builds the connection URLs for the control-plane services from those values.
For Docker Compose deployments, the services read standard PostgreSQL connection URLs from the environment. Use the same role and database for each service:
AISIX_CLOUD_DATABASE_URL="postgres://aisix:<url-encoded-password>@db.internal.example.com:5432/aisix_cloud?sslmode=require"
AISIX_DPMGR_DATABASE_URL="postgres://aisix:<url-encoded-password>@db.internal.example.com:5432/aisix_cloud?sslmode=require"
AISIX_CLOUD_DATABASE_URL: Read bycp-api, which runs the migration on startup, and by the dashboard.AISIX_DPMGR_DATABASE_URL: Read bydp-manager. If the schema is not ready yet becausecp-apiis still migrating on a fresh cluster,dp-managerretries for up to about 2 minutes.
Set sslmode=require (or stricter, such as verify-full) whenever the database is reached over a network you do not fully control.
What the Control Plane Creates
On the first successful start, cp-api builds the full schema in the database you provisioned, and re-runs the same steps idempotently on every later start:
- the
publicschema for control-plane tables, such as organizations, environments, models, caller API keys, and budgets, owned by your role; - the
authschema for authentication tables, such as users and sessions; - the
cp_api_approle, withNOSUPERUSER NOBYPASSRLS NOINHERIT NOLOGIN, granted only CRUD on control-plane tables and read/write on a few non-secret identity columns; - Row-Level Security policies that scope each tenant table to a single organization.
The connection role is automatically made a member of cp_api_app so it can switch to that role per request. You do not grant this yourself.
Database Privilege Checklist
Use this checklist when reviewing the database role with your DBA team:
| Requirement | Purpose |
|---|---|
LOGIN | Allows control-plane services to connect as this role. |
| Database ownership | Allows cp-api to create and alter schemas, tables, indexes, functions, and RLS policies. |
CREATEROLE | Allows cp-api to create and manage the internal cp_api_app role. |
BYPASSRLS | Allows cross-organization control-plane paths and dp-manager to read the rows they need on the shared connection. |
Not SUPERUSER | Keeps the role below PostgreSQL superuser privilege. |
Verify the Database Setup
After the control plane starts, connect as an administrator and confirm the bootstrap looks right.
The internal role exists and is correctly de-privileged:
SELECT rolname, rolsuper, rolbypassrls, rolcanlogin
FROM pg_roles WHERE rolname = 'cp_api_app';
-- expect: cp_api_app | f | f | f
Both schemas were created:
SELECT nspname FROM pg_namespace WHERE nspname IN ('public', 'auth');
-- expect: two rows
Troubleshoot Startup Errors
Use the startup error message to find the missing database capability.
cp-api database role must be SUPERUSER or have BYPASSRLS ...
The connection role is missing BYPASSRLS. Grant it as a database administrator:
ALTER ROLE aisix BYPASSRLS;
permission denied for schema public
The role cannot create objects in the public schema. Confirm database ownership. If the schema is locked down, grant schema access explicitly:
\c aisix_cloud
GRANT USAGE, CREATE ON SCHEMA public TO aisix;
permission denied to create role
The connection role is missing CREATEROLE. Grant it as a database administrator:
ALTER ROLE aisix CREATEROLE;
permission denied to set role "cp_api_app"
The migration that grants cp_api_app membership likely did not finish. Check the cp-api startup logs for an earlier migration error.
Next Steps
After the database and role are ready, return to On-Premises Deployment and configure the control plane to use the external database.
For the full set of Docker Compose environment variables and Helm values, see On-Premises Configuration.