2. Database Configuration¶
Decision Control Tower stores its own state — registered environments, promotion requests, audit log — in a relational database. Schema management is automated via Flyway: when the application starts it inspects the database, creates the baseline if needed and applies any outstanding migrations before serving traffic.
Supported engines and versions¶
| Engine | Versions | Notes |
|---|---|---|
| PostgreSQL | 14, 15, 16, 17, 18 | Supported engine. All deployments documented here use PostgreSQL. |
The reference is PostgreSQL 18; the CI matrix exercises 14 and 18 to cover the supported range.
The Tower Sidecar component (see chapter 4) requires its own dedicated PostgreSQL database — do not share schemas between Tower and Sidecar.
Schema requirements¶
| Item | Value |
|---|---|
| Default database name | decision_tower_db |
| Default schema | public |
| Default username | tower |
| Encoding | UTF8 |
| Collation / LC_CTYPE | any (the schema is collation-agnostic) |
| Required extensions | none |
The application user only needs read/write/DDL on its own schema:
CONNECTon the databaseUSAGEandCREATEon thepublicschema- The user is expected to be the owner of the database. Flyway requires DDL to create the migration history table on first startup.
If your security policy forbids dynamic DDL by application users, apply the schema manually before first startup using the SQL files under dc-tower-core/src/main/resources/db/migration/ and switch Flyway off via spring.flyway.enabled=false. Note that this requires careful coordination on every release that ships a new migration.
Provisioning steps¶
The following SQL provisions a fresh database compatible with Tower's defaults. Run as a PostgreSQL superuser:
-- 1. Create the application role with login
CREATE ROLE tower WITH LOGIN PASSWORD '<choose-a-strong-password>';
-- 2. Create the database owned by the application role
CREATE DATABASE decision_tower_db OWNER tower;
-- 3. Grant connect privilege explicitly (idempotent — owner already has it)
GRANT ALL PRIVILEGES ON DATABASE decision_tower_db TO tower;
Optionally, grant a separate read-only role for monitoring or BI tools (see "Operational access patterns" below).
For deployments that must use a non-default database name, override ALETYX_DC_TOWER_DB_URL:
The application uses spring.jpa.hibernate.ddl-auto=validate, which means Hibernate verifies that the runtime entity model matches the schema produced by Flyway. If validation fails, the application fails to start with a clear error in the logs — do not ignore these.
Migration and upgrade considerations¶
Flyway history table¶
The first time Tower starts against the database, Flyway creates the table flyway_schema_history in the public schema. Each migration is recorded with its checksum, so subsequent startups apply only newly added migrations.
Migration files shipped with the application¶
The current image carries the following migrations under db/migration/:
| File | Purpose |
|---|---|
V1__baseline.sql |
Initial schema: environments, environment_tags, promotion_requests, promotion_audit_log |
V2__add_unit_version_ids.sql |
Adds unit_id / version_id columns to promotion_requests |
V3__audit_trail_restructure.sql |
Replaces the individual promotion columns with original_details / deploy_result JSONB snapshots and drops promotion_audit_log (audit trail moves to Kogito Data Index) |
V4__notifications_and_submitted_by_name.sql |
Creates the email_templates table and adds submitted_by_name to promotion_requests |
The number of files grows over time — consult the image you are deploying for the current set.
Upgrade procedure¶
- Back up the database before any upgrade. Tower migrations are forward-only; rollback in production requires restoring from backup.
- Stop the running Tower instance, or scale the Deployment to zero replicas (Kubernetes).
- Verify there are no long-running transactions against
decision_tower_dbfrom external tools. - Start the new image. Tower applies pending migrations on startup; the application logs each migration as it runs and records it in
flyway_schema_history. - Confirm the application reaches the
Started AletyxTowerCoreApplicationlog line and that/actuator/healthreturns200 OKbefore allowing traffic.
If a migration fails partway, Tower fails fast and exits. The schema is left in the partially-migrated state recorded in flyway_schema_history (the failed migration is marked with success = false). Restore from backup, fix the underlying issue (often a schema drift introduced by manual ALTER TABLE outside Flyway), and retry.
Skipping migrations / repairing state¶
Two operational situations may require a Flyway repair:
- A migration was edited in place after being applied (checksum mismatch). The fix is to revert the migration to its applied form, or run
flyway repairagainst the database. - A migration file was renamed or deleted between releases. Coordinate with Aletyx support before applying — this almost always indicates an unintended downgrade.
Connection pooling¶
Tower uses HikariCP (Spring Boot default). Pool sizing is automatic but can be tuned via standard Spring properties if needed:
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.connection-timeout=30000
For most installations the defaults are appropriate. Significant query latency or connection-acquisition warnings in the logs indicate either an undersized pool or a database resource issue (CPU, IO, or pg_stat_activity showing long-running transactions).
Operational access patterns¶
Two access patterns are expected in addition to the application user:
- Read-only access for diagnostic tools or BI dashboards. Create a dedicated role and grant minimal privileges:
CREATE ROLE tower_reader WITH LOGIN PASSWORD '<password>';
GRANT CONNECT ON DATABASE decision_tower_db TO tower_reader;
\c decision_tower_db
GRANT USAGE ON SCHEMA public TO tower_reader;
ALTER DEFAULT PRIVILEGES FOR ROLE tower IN SCHEMA public
GRANT SELECT ON TABLES TO tower_reader;
ALTER DEFAULT PRIVILEGES FOR ROLE tower IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO tower_reader;
The ALTER DEFAULT PRIVILEGES FOR ROLE tower ensures that tables created by future Flyway migrations are visible to tower_reader automatically. Without it, each new migration would require a manual GRANT.
- Backups via
pg_dump. Tower's data is fully relational (no large objects, no binary columns), so a logical dump is safe and round-trips cleanly:
pg_dump --format=custom --file=tower.dump decision_tower_db
pg_restore --create --dbname=postgres tower.dump # restore on the target
Frequency depends on your retention policy; at minimum, a daily snapshot is recommended for any environment that holds production approval workflows.
Network connectivity¶
Tower expects to reach the database over TCP/IP on port 5432 (or whatever port the JDBC URL specifies). For deployments where database and application live in the same Kubernetes cluster, prefer in-cluster DNS (<service>.<namespace>.svc.cluster.local) over external addresses to keep traffic on the cluster's pod network.
TLS to the database is not enabled by default. If your security posture requires it, append the standard PostgreSQL JDBC parameters to the URL:
The application image's truststore includes the publicly-trusted certificate authorities. For self-signed CAs, you will need to extend the truststore at image build time or via the standard JAVA_TOOL_OPTIONS=-Djavax.net.ssl.trustStore=....
Configuration variables¶
| Variable | Required | Default | Description |
|---|---|---|---|
ALETYX_DC_TOWER_DB_URL |
yes (production) | jdbc:postgresql://localhost:5434/decision_tower_db |
JDBC connection URL |
ALETYX_DC_TOWER_DB_USERNAME |
yes (production) | tower |
Database username |
ALETYX_DC_TOWER_DB_PASSWORD |
yes (production) | tower |
Database password (provide via secret manager — see chapter 3) |
Example: docker-compose for a self-hosted PostgreSQL alongside Tower¶
services:
tower-database:
image: postgres:18
environment:
POSTGRES_DB: decision_tower_db
POSTGRES_USER: tower
POSTGRES_PASSWORD: <strong-password>
volumes:
- tower-db-data:/var/lib/postgresql/data
ports:
- "5434:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tower -d decision_tower_db"]
interval: 10s
timeout: 5s
retries: 5
decision-control-tower:
image: <your-registry>/aletyx/decision-control-tower:latest
depends_on:
tower-database:
condition: service_healthy
environment:
ALETYX_DC_TOWER_DB_URL: jdbc:postgresql://tower-database:5432/decision_tower_db
ALETYX_DC_TOWER_DB_USERNAME: tower
ALETYX_DC_TOWER_DB_PASSWORD: <strong-password>
# plus OIDC variables, see chapter 1
ports:
- "8080:8080"
volumes:
tower-db-data:
This is suitable for evaluation and small single-host deployments. For production the database should run on a managed service or a dedicated PostgreSQL host, separate from the Tower application host.