# Database Patterns ## PostgreSQL ### Always use `?sslmode=disable` `[WC-SSL]` Wild Cloud's internal PostgreSQL has no SSL configured. Without `?sslmode=disable`, connections fail with "The server does not support SSL connections." ```yaml defaultSecrets: - key: dbUrl default: "postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable" ``` Also set `PGSSLMODE=disable` for apps that use libpq directly. ### db-init-job Include a `db-init-job.yaml` for every app that uses PostgreSQL. See `immich`, `gitea`, or `openproject` for reference implementations. The job must: - Create the database if it doesn't exist - Create/update the user with correct credentials - Grant permissions - Install required extensions (`vector`, `pg_trgm`, etc.) - Use `restartPolicy: OnFailure` and `runAsUser: 999` - Be idempotent — safe to re-run after redeploy ### Database URL secrets When an app needs a connection URL with embedded credentials, use a `dbUrl` secret — do not construct URLs inline: ```yaml # Wrong: Kustomize cannot do runtime env var substitution - name: DB_URL value: "postgresql://user:$(DB_PASSWORD)@host/db" # Correct: use a secret with the full URL - name: DB_URL valueFrom: secretKeyRef: name: myapp-secrets key: dbUrl ``` ## MySQL ### db-init user password idempotency `[WC-DBIN]` `CREATE USER IF NOT EXISTS` only sets the password on first creation. On redeploy against an existing database the password stays stale, causing "Access denied". Always follow `CREATE USER` with `ALTER USER`: ```sql CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}'; ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}'; GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%'; FLUSH PRIVILEGES; ``` `ALTER USER` is a no-op when the user was just created — it is safe to always include it. ### Required secrets reference MySQL secrets are copied into `-secrets`, not `mysql-secrets`. Reference them as: ```yaml secretKeyRef: name: myapp-secrets key: mysql.rootPassword # not mysql-secrets / rootPassword ``` ## Database env var naming Name database-related env vars so the backup system can identify them: - **Database name**: include `DATABASE`, `DB_NAME`, `DBNAME`, or `__DATABASE` - **Database URLs**: value must contain `://` - **Usernames**: include `USER` — these are not patched on restore