Skip to content

Database Configuration

Decision Control persists its state (models, decisions, published versions) in a relational database. PostgreSQL is the supported engine — every deployment described in this guide uses it.

Supported versions

Engine Versions Notes
PostgreSQL 14–18 16+ recommended for new deployments

Connection settings

Decision Control reads the standard Spring datasource environment variables (Spring relaxed binding maps them to spring.datasource.*):

Variable Required Example
SPRING_DATASOURCE_URL yes jdbc:postgresql://postgres.example.com:5432/dc
SPRING_DATASOURCE_USERNAME yes dc_app
SPRING_DATASOURCE_PASSWORD yes <from secret store>

A production deployment must point these at a managed PostgreSQL instance reachable from the application. In Kubernetes, the URL typically uses the in-cluster service DNS (e.g. jdbc:postgresql://postgres.infra.svc.cluster.local:5432/dc).

Schema management

The schema is created and migrated automatically on startup, controlled by HIBERNATE_DDL_AUTO:

Value Behavior When to use
validate Verify the schema matches the entity model; fail fast on mismatch. No DDL. Production (schema already exists)
update Apply additive schema changes on startup. Development / first bootstrap
create Drop and recreate the schema on every startup. Destroys data. Ephemeral test environments only

For production, provision an empty database, run a first boot with update to create the schema (or apply the schema out-of-band), then switch to validate for subsequent deployments so an unexpected entity change can never silently alter the production schema.

Provisioning

Create a dedicated database and least-privilege application role:

-- As a Postgres superuser / admin
CREATE ROLE dc_app WITH LOGIN PASSWORD '<choose-a-strong-password>';
CREATE DATABASE dc OWNER dc_app;

-- Restrict the role to its own database
REVOKE ALL ON DATABASE dc FROM PUBLIC;
GRANT CONNECT ON DATABASE dc TO dc_app;

The application role needs CONNECT, CREATE (for first-boot schema creation under update), and full DML on its own schema. Once the schema is created and you switch to validate, you may drop CREATE from the role.

Optional read-only role

For monitoring or BI access without write privileges:

CREATE ROLE dc_readonly WITH LOGIN PASSWORD '<password>';
GRANT CONNECT ON DATABASE dc TO dc_readonly;
\c dc
GRANT USAGE ON SCHEMA public TO dc_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO dc_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO dc_readonly;

Upgrade considerations

  • Schema changes ship with the application image. Deploy a new image with HIBERNATE_DDL_AUTO=update for the first rollout that introduces a schema change, then return to validate.
  • Take a database backup before any upgrade that changes the schema.
  • Schema changes are additive within a major line; a downgrade after a schema change is not supported without restoring from backup.

TLS to the database

Append SSL parameters to the JDBC URL when the database requires encrypted connections:

jdbc:postgresql://postgres.example.com:5432/dc?ssl=true&sslmode=verify-full&sslrootcert=/etc/ssl/certs/db-ca.crt

Mount the CA certificate into the container and reference it with sslrootcert. Use verify-full in production so the driver validates the server hostname against the certificate.

Verifying

After deploying, confirm the application reaches the Started AletyxDcApplication log line and that /actuator/health returns 200 OK before allowing traffic. A datasource problem surfaces as a Hibernate / HikariCP connection error in the logs and a DOWN health status.

Backup and recovery

Treat the Decision Control database the same as any other application Postgres: nightly logical backups plus point-in-time recovery for production. The application is stateless apart from this database — restoring it is the only step needed to recover the deployment state (models, decisions, versions, audit trail).

Scheduled pg_dump (Kubernetes example)

A simple CronJob running the postgres image is enough for the application database. The example below assumes the database hostname is <your-db-host>, the connection credentials live in a Secret named dc-db-secret, and the persistent backup volume is dc-backup-pvc:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: dc-postgres-backup
  namespace: <your-namespace>
spec:
  schedule: "0 2 * * *"  # daily at 02:00
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: backup
              image: postgres:15-alpine
              command:
                - /bin/sh
                - -c
                - |
                  pg_dump -h <your-db-host> \
                    -U "$POSTGRES_USER" \
                    -d <your-database> \
                    -F c -f /backup/dc-$(date +%Y%m%d).dump
              env:
                - name: POSTGRES_USER
                  valueFrom: { secretKeyRef: { name: dc-db-secret, key: POSTGRES_USER } }
                - name: PGPASSWORD
                  valueFrom: { secretKeyRef: { name: dc-db-secret, key: POSTGRES_PASSWORD } }
              volumeMounts:
                - { name: backup-storage, mountPath: /backup }
          volumes:
            - name: backup-storage
              persistentVolumeClaim:
                claimName: dc-backup-pvc
          restartPolicy: OnFailure

The -F c (custom format) output is required by pg_restore. Keep at least 7 daily backups plus one weekly retained for 30 days; the volume sizing is dominated by version history rather than active model count.

Restore

# Copy the dump into a pod that can reach the database (the postgres image is fine).
kubectl cp dc-20260101.dump <namespace>/<helper-pod>:/tmp/

kubectl exec -it <helper-pod> -n <namespace> -- /bin/sh

# Inside the pod — `-c` drops conflicting objects before restoring.
pg_restore -h <your-db-host> -U <your-user> -d <your-database> -c /tmp/dc-20260101.dump

Restoring into a database that already has data (an upgrade rehearsal, for example) is supported with the --clean flag shown above; the schema and rows are dropped before being recreated. Restoring into an empty database produces the same final state.

Managed PostgreSQL services

For Cloud SQL / RDS / Azure Database, prefer the platform's own snapshot and point-in-time recovery rather than running pg_dump from inside the cluster. The result is faster and integrates with your platform's RBAC and audit. Use logical dumps for cross-environment migrations only (dev → staging refresh, etc.).