Skip to content

Database Configuration

The database is what makes a replicated deployment possible: it holds the catalog, applications, subscriptions, credentials, user sessions, and the webhook event queue. Every portal pod reads and writes the same one, which is why no pod needs sticky sessions or a shared filesystem.

This page covers the production-specific setup. For driver choice and the schema itself, see Set up the database.

Use PostgreSQL or SQL Server. The examples below show PostgreSQL; Set up the database has the equivalents for SQL Server, including the driver values and port.

Create the database

Provision an external, highly available server the cluster can reach, and create a database and a login role for the portal:

CREATE DATABASE api_portal;
CREATE USER api_portal WITH PASSWORD '<strong-password>';
GRANT ALL PRIVILEGES ON DATABASE api_portal TO api_portal;

The portal owns this database outright. If another component of your estate has its own store, keep them separate — the portal never reads or writes anyone else's.

Apply the schema

Apply the schema before first start:

psql -h postgres.example.com -U api_portal -d api_portal -f database/schema.postgres.sql

A reference copy ships in the distribution under resources/api-portal/db-scripts/, and the same file is bundled inside the image. See Apply the schema.

Store the password in a Secret

The password never appears in a values file. It comes from the release's Secret, under the key APIP_AP_DATABASE_PASSWORD — the same name as the environment variable the container receives. generate-secrets.sh prompts for it and writes it into the Secret it creates. See Security Hardening → Secrets.

Configure the chart

api-portal-ui:
  config:
    database:
      type: postgres
      host: postgres.example.com
      port: 5432
      database: api_portal       # the schema name — rendered into config as `name`
      user: api_portal
      sslmode: verify-full       # disable | require | verify-ca | verify-full
      sslRootCert: /etc/api-portal/certs/ca.pem

Two key names catch people out: the schema name is database, not name, and sslmode is all lowercase.

Switching type off sqlite is also what unlocks scaling — the chart refuses to render an autoscaler on a database that can't be shared.

The shipped configs/config.toml carries only driver and path, so setting APIP_AP_DATABASE_HOST on its own does nothing — an environment value reaches a setting only where a token references it. Replace the whole [api_portal.database] block with the form for your driver from configs/config-template.toml:

[api_portal.database]
driver   = '{{ env "APIP_AP_DATABASE_DRIVER" "postgres" }}'
host     = '{{ env "APIP_AP_DATABASE_HOST" "localhost" }}'
port     = '{{ env "APIP_AP_DATABASE_PORT" "5432" }}'
name     = '{{ env "APIP_AP_DATABASE_NAME" "api_portal" }}'
user     = '{{ env "APIP_AP_DATABASE_USER" "" }}'
password = '{{ env "APIP_AP_DATABASE_PASSWORD" "" }}'
ssl_mode      = "verify-full"
ssl_root_cert = "/etc/api-portal/tls/ca.pem"

Then set the values in api-platform.env on each VM. Note the TOML keys are snake_case (ssl_mode, ssl_root_cert) where the Helm values are not.

user has no fallback on purpose: a non-SQLite driver with no user, host, or name refuses to start rather than silently connecting as a conventional superuser name to whatever server the defaults point at.

TLS to the database

Use verify-full in production. It is the only mode that validates both the certificate chain and the hostname, so it defends against a redirected connection rather than merely encrypting one:

Mode What it checks
disable Nothing — cleartext
require Encryption only; accepts any certificate
verify-ca Certificate chains to a trusted CA
verify-full Chain and that the hostname matches the certificate

verify-ca and verify-full need sslRootCert pointing at the CA certificate, mounted into the pod.

Connection pool tuning

Each pod opens its own pool, so the cluster's total connection demand is:

replicas × maxOpenConns

With the default maxOpenConns: 50, three replicas ask for up to 150 connections. Keep the total comfortably below the server's max_connections, leaving headroom for administrative sessions and for a rolling upgrade — during which old and new pods briefly overlap.

api-portal-ui:
  config:
    database:
      maxOpenConns: 25
      minOpenConns: 2
      poolIdleTimeoutMs: 10000
      poolConnectionTimeoutMs: 30000
      poolRequestTimeoutMs: 30000

Lowering maxOpenConns as you raise the replica count is usually the right trade: more pods each holding a smaller pool. minOpenConns keeps a floor of warm connections per pod, so the first request after an idle period doesn't pay connection setup.