BlogSecrets

Dynamic database credentials for Jenkins with Vault

Stop pasting Postgres passwords into Jenkins credentials. Vault mints a fresh user per build and revokes it when the job ends.

Mar 24, 2026·11 min readIntermediate

A static Postgres password pasted into Jenkins credentials is a secret with no expiry, shared by every build, that shows up in a heap dump the first time a job crashes. Vault's database secrets engine replaces it with a fresh user per build — created on demand, revoked when the lease ends.

Credential lifecycle
1
Build starts
Jenkins asks Vault
2
Vault → DB
CREATE ROLE, short TTL
3
Build runs
uses temp user
4
Lease ends
Vault DROPs the role

Enable the database secrets engine

Point Vault at your database with an admin connection it uses only to mint and drop users. The allowed_roles list is the guardrail — this connection can't be used to create arbitrary roles.

vault-setup.sh
vault secrets enable database
vault write database/config/appdb \
plugin_name=postgresql-database-plugin \
allowed_roles="jenkins-ci" \
connection_url="postgresql://{{username}}:{{password}}@db:5432/app?sslmode=require" \
username="vault_admin" password="$ADMIN_PW"

A role that mints short-lived users

The role defines the SQL Vault runs to create a user and the TTL before it's revoked. Keep the TTL just longer than your slowest build — minutes, not hours.

vault-role.sh
vault write database/roles/jenkins-ci \
db_name=appdb \
default_ttl="10m" max_ttl="20m" \
creation_statements="\
CREATE ROLE \"{{name}}\" LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"

Read the role once and Vault hands back a brand-new username and password with a lease attached:

bash — vault readlive
vault read database/creds/jenkins-ci
 
Key Value
--- -----
lease_id database/creds/jenkins-ci/8Kx2..
lease_duration 10m
password A1a-9f3Kp2mZ-qX7
username v-jenkins--ci-6Yh0
 
user auto-dropped when the 10m lease expires

Wire it into the pipeline

The HashiCorp Vault plugin injects the secret as an env var scoped to a single stage. It never touches the Jenkins credential store and never lands on disk.

Jenkinsfile
withVault(vaultSecrets: [[
path: 'database/creds/jenkins-ci',
secretValues: [
[envVar: 'DB_USER', vaultKey: 'username'],
[envVar: 'DB_PASS', vaultKey: 'password'],
]
]]) {
sh './run-migrations.sh' // DB_USER / DB_PASS live only in this block
}
Revoke on failure, too
A crashed build still holds a lease. Give the token a short TTL and enable lease revocation on job completion so a failed pipeline never leaves a live database user behind.

Where this goes next

The same pattern extends to cloud IAM (aws secrets engine), PKI certificates, and SSH one-time passwords. Once builds stop carrying long-lived secrets, credential rotation becomes something Vault does for you instead of a quarterly fire drill.

Related posts