--- title: "ADR-0016: Declarative Database List in the Ansible postgres Role" --- # ADR-0016: Declarative Database List in the Ansible postgres Role **Status:** Proposed **Date:** 2026-06-04 **Deciders:** Thanasis Katsadas ## Context The Themelio platform runs a shared CloudNativePG (CNPG) PostgreSQL cluster in the `databases` Kubernetes namespace. The cluster currently hosts databases for GitLab and Waldur. The Ansible `postgres` role (`devops/ansible/roles/postgres/`) deploys this cluster and provisions each database. The role currently defines all database configuration as flat, per-database Ansible variables: `gitlab_db_user`, `gitlab_db_name`, `gitlab_db_resource_name`, `waldur_db_user`, `waldur_db_name`, and so on. Each database also requires its own Jinja2 template for the CNPG `Database` CRD and a separate template for the Kubernetes `Secret` that holds credentials. The tasks file contains a dedicated task block for each database — validation, secret creation, and resource application — all named after specific databases. The platform is expanding. The `k8s-root-actor` service requires its own PostgreSQL database on the shared cluster, and additional per-service databases are likely in the future. Under the current structure, adding any database requires changes in four separate places inside the role: flat variables in `defaults/main.yml`, two new templates, and three new task entries. This pattern does not scale and introduces inconsistency risk. **See also:** [DD-0016: Implementation](../content/dd/0016-declarative-database-list-in-postgres-role.md) ## Decision Drivers - Adding a new database should require only a single list entry in inventory, with no role-internal changes. - The role should remain self-consistent: adding a database entry is the only action needed. - Vault password management should remain per-environment without changing structure. - The role must not become responsible for configuring applications to connect to their databases; that remains with each service's own role. ## Decision We will replace all flat per-database variables with a `postgres_databases` list defined in inventory. Each list entry describes one CNPG `Database` resource. The role iterates over this list to validate passwords, create Kubernetes secrets, configure CNPG managed roles, and apply Database CRDs. Vault passwords become a per-environment `vault_postgres_passwords` dict keyed by PostgreSQL username. The role's templates are consolidated from five database/secret-specific files to two generic ones. The postgres role remains responsible only for CNPG cluster infrastructure — the cluster resource, Database CRDs, and credentials in the `databases` namespace. Each service role is responsible for projecting or mounting those credentials in its own namespace and application configuration. ## Consequences ### Positive - Adding a new database requires editing only `inventory/common/group_vars/all.yml` (one list entry) and the relevant environment vault file (one new password key). No changes to the role itself. - The role is closed to extension: it does not need to be modified when Themelio adds new services with databases. - Templates shrink from five files to two, and tasks shrink from seven per-database task blocks to three loops. - CNPG managed roles are derived automatically from the list, eliminating the hardcoded user section in `cnpg-cluster.yml.j2`. ### Negative / Risks - The `postgres_databases` list schema must be documented clearly; a missing field causes a runtime error rather than a clear validation message. - Deduplication logic for secrets (by `secret_name`) and CNPG roles (by `user`) requires reviewers to understand Jinja2 `unique` filter semantics. - Existing vault files must be migrated from two flat vars (`vault_gitlab_db_password`, `vault_waldur_db_password`) to the new `vault_postgres_passwords` dict before the refactored role runs. This is a one-time migration; the actual passwords stay the same.