This documentation describes the internal GitHub Actions workflow for provisioning application databases across three PostgreSQL environments (dev, test, prod).
The goal is a repeatable, secure, and idempotent setup of:
- an application user (login role),
- a dedicated database,
- basic privileges (CONNECT, TEMP, CREATE),
- and a consolidated summary of all provisioned resources.
During execution, the necessary firewall rule for the CI runner is temporarily opened and automatically removed afterward.
flowchart TD
Start([workflow_dispatch]) --> Inputs[/"app_name,
dev/test/prod_action: skip | create | reset,
dev/test/prod_password"/]
Inputs --> Validate{Input validieren<br/>regex + Passwort}
Validate -- ungültig --> Fail1[Abbruch]
Validate -- gültig --> Matrix
subgraph Matrix["Provision Matrix (dev → test → prod, sequentiell)"]
direction TB
Resolve{Aktion für<br/>Umgebung?}
Resolve -- skip --> Skip[Übersprungen]
Resolve -- create / reset --> Civo
Civo[Civo CLI installieren<br/>+ API Key setzen]
Civo --> DetectIP[Runner Public IPv4<br/>ermitteln]
DetectIP --> FWOpen[Firewall: Port 5432<br/>temporär öffnen]
FWOpen --> Psql[psql Client<br/>installieren]
Psql --> PreState[Pre-State prüfen<br/>Role/DB existiert?]
PreState --> Upload1[Pre-State Artifact<br/>hochladen]
Upload1 --> Provision
subgraph Provision[Provisioning]
direction TB
P1[Passwörter maskieren]
P1 --> P2{Role existiert?}
P2 -- nein --> P3[CREATE ROLE mit Passwort]
P2 -- ja --> P4[ALTER ROLE Passwort setzen]
P3 --> P5{DB existiert?}
P4 --> P5
P5 -- "nein (create/reset)" --> P6[CREATE DATABASE]
P5 -- "ja + action=create" --> P7[DB beibehalten]
P5 -- "ja + action=reset" --> P8[DROP + CREATE DATABASE]
P6 --> P9[GRANT Privilegien<br/>CONNECT, TEMP, CREATE]
P7 --> P9
P8 --> P9
end
Provision --> Upload2[Summary Artifact<br/>hochladen]
Upload2 --> FWClose[Firewall: temporäre<br/>Regel entfernen]
end
Matrix -- Erfolg --> Summary
Matrix -- Fehlschlag --> Rollback
subgraph Summary["Summary Job"]
S1[Alle Summary Artifacts<br/>herunterladen] --> S2[Gesamtübersicht als<br/>GitHub Step Summary]
end
subgraph Rollback["Rollback Job (bei Fehlschlag)"]
direction TB
R0[Civo CLI + psql<br/>installieren]
R0 --> R1[IPv4 ermitteln +<br/>Firewall öffnen]
R1 --> R2[Pre-State Artifact<br/>herunterladen]
R2 --> R3{Pre-State<br/>vorhanden?}
R3 -- nein --> R4[Kein Rollback möglich]
R3 -- ja --> R5{DB war neu<br/>erstellt / reset?}
R5 -- ja --> R6[Verbindungen trennen<br/>+ DROP DATABASE]
R5 -- nein --> R7{Role war neu<br/>erstellt?}
R6 --> R7
R7 -- ja --> R8[DROP ROLE]
R7 -- nein --> R9[Rollback Summary<br/>schreiben]
R8 --> R9
R4 --> R10[Firewall: temporäre<br/>Regel entfernen]
R9 --> R10
end
Summary --> Done([Fertig])
Rollback --> Done
style Start fill:#2d6a4f,color:#fff
style Done fill:#2d6a4f,color:#fff
style Fail1 fill:#d00,color:#fff
style Skip fill:#666,color:#fff
style Rollback fill:#fdf0d5,stroke:#c77800
style Summary fill:#d5f5e3,stroke:#27ae60
style Matrix fill:#e8f4fd,stroke:#2980b9
style Provision fill:#dbeafe,stroke:#3b82f6
- Trigger: Manual via GitHub Actions (
workflow_dispatch) - Environments: dev, test, prod (matrix strategy)
- Naming conventions:
- User:
{app_name}_user - Database:
{app_name}_db - Schema:
public
- User:
- Security:
- SSL enforced (
sslmode=require) - Password masking in logs
- Temporary IP-bound firewall rule
- SSL enforced (
- Robustness:
- Automatic retries on DB operations
- Create or update the application login per environment
- Create a dedicated UTF-8 database (if not already existing)
- Grant
CONNECT,TEMP, andCREATEprivileges on the database to the application user - Temporarily open TCP/5432 for the runner’s IP in the target firewall
- Merge per-environment results into a unified summary (Actions Summary)
- For each environment (dev, test, prod): a reachable PostgreSQL server with SSL support
- Admin account with privileges to:
CREATE ROLE/ALTER ROLE(set/change passwords)CREATE DATABASEGRANTon databases
- Access to the corresponding network/firewall configuration
- GitHub Actions enabled, with permissions to set secrets and variables
Secrets (Actions > Secrets and variables > Actions > Secrets):
CIVO_API_KEY: API token for cloud firewall accessDEV_SERVER_ADMIN_PASSWORD: Admin password for devTEST_SERVER_ADMIN_PASSWORD: Admin password for testPROD_SERVER_ADMIN_PASSWORD: Admin password for prod
Variables (Actions > Secrets and variables > Actions > Variables):
DEV_SERVER_HOST: Host/IP of the PostgreSQL server (dev)TEST_SERVER_HOST: Host/IP (test)PROD_SERVER_HOST: Host/IP (prod)ADMIN_USER: Admin username (e.g. postgres)DEV_FIREWALL_NAME: Firewall identifier for devTEST_FIREWALL_NAME: Firewall identifier for testPROD_FIREWALL_NAME: Firewall identifier for prod
Notes:
- Default port is
5432. If different, update both workflow config and firewall rule. - Secrets are used only at runtime and are masked in logs.
- Verify that all required secrets and variables are set and correct.
- Ensure DB servers are reachable (including SSL).
- Confirm firewall identifiers per environment.
- Go to Actions, open the Provision Workflow, and select Run workflow.
app_name(required): Base name of the application, e.g.shop- Generates: user
shop_user, databaseshop_db
- Generates: user
dev_action/test_action/prod_action: Action per environmentskip(default) — do nothing for this environmentcreate— create role + database (if DB exists, keep as-is)reset— create role + database (if DB exists, drop and recreate for clean state)
dev_password/test_password/prod_password: Password per environment (required unless action isskip)
For each environment where action is not skip:
- The runner’s public IPv4 is determined.
- Temporary firewall rule for TCP/5432 is created for this IP.
psqlclient is installed.- Create or update role
{app_name}_user(password set idempotently). - Database handling based on action:
create: Create{app_name}_dbwith UTF-8 if not existing; keep existing DB unchanged.reset: Drop existing DB (terminate connections first) and recreate from scratch.
GRANT CONNECT, TEMP, CREATEon DB to{app_name}_user.- Temporary firewall rule is removed.
- Compact summary generated in GitHub Actions Summary.
A separate workflow to completely remove an application's database and role from selected environments.
flowchart TD
Start([workflow_dispatch]) --> Inputs[/"app_name,
dev: true/false,
test: true/false,
prod: true/false"/]
Inputs --> Validate{Input validieren<br/>regex check}
Validate -- ungültig --> Fail1[Abbruch]
Validate -- gültig --> Matrix
subgraph Matrix["Delete Matrix (dev → test → prod, sequentiell)"]
direction TB
Resolve{Umgebung<br/>ausgewählt?}
Resolve -- nein --> Skip[Übersprungen]
Resolve -- ja --> Civo
Civo[Civo CLI installieren<br/>+ API Key setzen]
Civo --> DetectIP[Runner Public IPv4<br/>ermitteln]
DetectIP --> FWOpen[Firewall: Port 5432<br/>temporär öffnen]
FWOpen --> Psql[psql Client<br/>installieren]
Psql --> Delete
subgraph Delete[Löschvorgang]
direction TB
D1{DB existiert?}
D1 -- ja --> D2[Verbindungen trennen<br/>+ DROP DATABASE]
D1 -- nein --> D3[Übersprungen]
D2 --> D4{Role existiert?}
D3 --> D4
D4 -- ja --> D5[DROP ROLE]
D4 -- nein --> D6[Übersprungen]
D5 --> D7[Step Summary<br/>schreiben]
D6 --> D7
end
Delete --> FWClose[Firewall: temporäre<br/>Regel entfernen]
end
Matrix --> Done([Fertig])
style Start fill:#2d6a4f,color:#fff
style Done fill:#2d6a4f,color:#fff
style Fail1 fill:#d00,color:#fff
style Skip fill:#666,color:#fff
style Matrix fill:#fde8e8,stroke:#c0392b
style Delete fill:#fadbd8,stroke:#e74c3c
- Go to Actions, open the Delete App Database and Role workflow, and select Run workflow.
- Enter
app_name(e.g.shop) — this targetsshop_dbandshop_user. - Check the environments to delete from (dev, test, prod).
- For each selected environment:
- If the database exists: terminate connections and
DROP DATABASE. - If the role exists:
DROP ROLE. - If either does not exist: skip gracefully (no error).
- If the database exists: terminate connections and
- Results are shown in the GitHub Actions Step Summary per environment.
- The GitHub Actions Summary includes a table listing:
- Environment, Host:Port, User, Database, Schema (
public), Action
- Environment, Host:Port, User, Database, Schema (
- Each environment generates a ready-to-use JDBC connection string in the summary.
- Each environment generates a short result file merged into the final summary.
- Logs record all steps, with sensitive data masked.
The provisioning workflow generates a JDBC connection string per environment with production-ready defaults:
jdbc:postgresql://<HOST>:<PORT>/<app_name>_db?sslmode=require&connectTimeout=10&socketTimeout=45&tcpKeepAlive=true&ApplicationName=<app_name>_<env>¤tSchema=public
| Parameter | Value | Purpose |
|---|---|---|
sslmode=require |
SSL enforced | Encrypted connections only, no plaintext fallback |
connectTimeout=10 |
10 seconds | Max wait time for initial connection establishment |
socketTimeout=45 |
45 seconds | Max wait time for a response after sending a query |
tcpKeepAlive=true |
TCP Keep-Alive | Prevents firewalls/NATs from silently dropping idle connections |
ApplicationName |
<app>_<env> |
Identifies the service in pg_stat_activity for debugging |
currentSchema=public |
public |
Explicit schema selection instead of relying on server default |
Example (app_name=snapshots):
jdbc:postgresql://74.220.25.6:5432/snapshots_db?sslmode=require&connectTimeout=10&socketTimeout=45&tcpKeepAlive=true&ApplicationName=snapshots_dev¤tSchema=public
Connection credentials: user <app_name>_user, password as provided during the workflow run.
- Role passwords are explicitly reset on every run (deliberately idempotent).
- Database handling depends on action:
createkeeps existing DBs;resetdrops and recreates for a guaranteed clean state. GRANTstatements are repeatable and safe.
Recommendations:
- For password rotation, simply rerun the workflow with new passwords.
- Keep hosts/ports/firewall names consistently updated in variables.
- Password rotation:
- Rerun workflow with new passwords.
- Port changes:
- Update port in both workflow configuration and firewall rule.
- Adding new environments (e.g. staging):
- Add another matrix definition (host, admin secret, firewall name, env_name).
- Add corresponding secrets and variables.
- Regular maintenance:
- Renew API keys and admin credentials periodically.
- Monitor DB versions, SSL requirements, and company policies.
- SSL enforced (
sslmode=require). - Passwords masked immediately after being read (including trimmed variants).
- Firewall rules restricted to runner IP (/32) and reliably removed (even on error).
- Admin credentials only provided via secrets—never in plain text variables or files.
Connection fails:
- Check host, port, and admin user variables.
- Verify connectivity (DNS, routing, security groups/firewall).
- Ensure SSL requirements; the server must accept encrypted connections.
Firewall issues:
- Correct firewall name set per environment?
- Was the temporary rule created (check logs)?
- Region of the firewall matches the target environment?
Permission errors:
- Does the admin have
CREATE ROLE,ALTER ROLE,CREATE DATABASE,GRANTprivileges? - If
CREATE DATABASEis restricted: pre-create DB manually, let workflow assign privileges.
Instability/Timeouts:
- Built-in retry mechanism (multiple attempts with delay).
- Check network stability, latency, and load.
Empty summary:
- Confirm that result files were created and summary step executed successfully.
What privileges does the application user receive?
CONNECT, TEMP, and CREATE on its dedicated database (further object-level privileges managed separately).
Which encoding is used?
UTF-8. Adjustments are possible but should be centrally coordinated.
What happens if an environment is unreachable?
After several retries, that environment’s job fails. Restart after stability is restored.
- Document all secret/variable changes (Who/What/When/Why).
- Review workflow modifications before merging.
- For major changes (e.g. privilege model, additional schemas), align with DB and security teams.
- Application user: Login role through which the app connects to the DB.
- Dedicated database: One database per app and environment for isolation.
- Matrix job: Executes the workflow for multiple defined environments.
- Summary: Automatically generated result overview in the GitHub Actions run.