One of the most powerful — and underutilized — features of HashiCorp Vault is its Database Secrets Engine. Instead of embedding static database passwords in your .env files, application configs, or CI/CD pipelines, Vault generates short-lived, on-demand credentials that are automatically revoked when they expire.
This guide walks through the full setup:
- Installing Vault in dev mode (local testing)
- Running MariaDB via Docker Compose
- Enabling and configuring the Database Secrets Engine
- Creating
db-adminanddb-read-onlyroles - Generating and verifying dynamic credentials against MariaDB
- Managing leases and manual revocation
Official reference: Vault MySQL/MariaDB Database Secrets Engine
Prerequisites
- Vault CLI installed on your machine
- Docker & Docker Compose (to run MariaDB)
- A Unix-like terminal (Linux, macOS, or WSL on Windows)
Configuration Placeholders
The table below lists the placeholder variables used throughout this guide. Replace them with your environment’s actual values:
| Placeholder | Description | Example |
|---|---|---|
<MARIADB_HOST> | IP or hostname of the MariaDB server | 192.168.1.100 |
<MARIADB_PORT> | MariaDB port | 3306 |
<MARIADB_ROOT_PASSWORD> | Root password for MariaDB | YourStrongPassword |
<VAULT_HOST> | IP or hostname of the Vault server (non-dev mode) | 192.168.1.50 |
<VAULT_PORT> | Vault API port | 8200 |
Step 1: Install Vault (Dev Mode)
For local testing and exploration, you don’t need a full production Vault setup (unsealing, persistent storage backends, etc.). The dev server starts pre-unsealed with an in-memory storage backend — perfect for learning.
macOS (Homebrew)
brew tap hashicorp/tapbrew install hashicorp/tap/vaultLinux (Debian/Ubuntu)
sudo apt updatesudo apt install -y gnupg wget lsb-release
wget -O- https://apt.releases.hashicorp.com/gpg \ | gpg --dearmor \ | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \ | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt updatesudo apt install -y vaultVerify Installation
vault --versionOutput:
Vault v2.0.4 (c9e9d1d4ddd4b55aae79a8949adffa9e96338720), built 2026-08-03T16:14:36ZStart the Dev Server
Open a dedicated terminal window and run:
vault server -dev -dev-root-token-id="root"Output:
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memoryand starts unsealed with a single unseal key. The root token is alreadyauthenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variables:
$ export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want toseal/unseal the Vault or re-authenticate.
Unseal Key: c+pRvn/2UC6tGpAH5ucQ9Qejufbxnpl/u0UuHTWnoKc=Root Token: root
Development mode should NOT be used in production installations!Vault will start listening on http://127.0.0.1:8200 with the root token root.
Warning: Dev mode stores all data in memory and resets on restart. Never use dev mode in production.
In a second terminal, export the required environment variables so the CLI knows where to connect:
export VAULT_ADDR='http://127.0.0.1:8200'export VAULT_TOKEN='root'Verify connectivity:
vault statusOutput:
Key Value--- -----Seal Type shamirInitialized trueSealed falseTotal Shares 1Threshold 1Version 2.0.4Build Date 2026-08-03T16:14:36ZStorage Type inmemCluster Name vault-cluster-b032d240Cluster ID 3ae78de0-3009-c9b6-5993-caba26940ba5HA Enabled falseExpected output will show Sealed: false — confirming Vault is running and unsealed.
Alternative: Run Vault Without Dev Mode (via config.hcl)
Dev mode is great for quick exploration, but it stores everything in-memory (wiped on process exit) and starts fully unsealed automatically — which doesn’t reflect how Vault behaves in a real environment. If you want a more realistic setup (persistent data, manual init/unseal), use a configuration file instead.
1. Create config.hcl
storage "file" { path = "/opt/vault/data"}
listener "tcp" { address = "<VAULT_HOST>:<VAULT_PORT>" tls_disable = true}
api_addr = "http://<VAULT_HOST>:<VAULT_PORT>"ui = trueConfiguration breakdown:
| Field | Purpose |
|---|---|
storage "file" | Persists all Vault data to disk. For HA environments, use storage "raft" instead |
listener "tcp" address | Replace <VAULT_HOST> with the server’s IP/hostname and <VAULT_PORT> with the desired port |
tls_disable = true | Allows plain HTTP access without a certificate — testing/development only |
api_addr | The address Vault advertises to clients and cluster peers; must match listener |
Create the storage directory:
sudo mkdir -p /opt/vault/datasudo chown -R $(whoami) /opt/vault/data2. Start Vault with the Config File
vault server -config=config.hclOutput:
==> Vault server configuration:
Cgo: disabled Go Version: go1.26.5 Listener 1: tcp (addr: "<VAULT_HOST>:8200", cluster address: "<VAULT_HOST>:8201", tls: "disabled") Mlock: supported: true, enabled: false Recovery Mode: false Storage: file Version: Vault v2.0.4, built 2026-08-03T16:14:36Z
==> Vault server started! Log data will stream in below:
[INFO] proxy environment: http_proxy="" https_proxy="" no_proxy=""[INFO] incrementing seal generation: generation=1[INFO] core: Initializing version history cache for core[INFO] events: Starting event system[INFO] core: security barrier not initialized[INFO] core: seal configuration missing, not initializedIn a new terminal, point the CLI at your Vault instance:
export VAULT_ADDR='http://<VAULT_HOST>:<VAULT_PORT>'3. Initialize Vault
Unlike dev mode, a fresh Vault instance must be initialized before use. This process generates the unseal keys and the initial root token — store them securely, as they are only shown once:
vault operator initExample output:
Unseal Key 1: xxxxxUnseal Key 2: xxxxxUnseal Key 3: xxxxxUnseal Key 4: xxxxxUnseal Key 5: xxxxx
Initial Root Token: hvs.xxxxxxxxxxxxxxxxBy default, Vault requires 3 out of 5 unseal keys (configurable via -key-shares and -key-threshold flags on vault operator init).
4. Unseal Vault
Run vault operator unseal once per key, up to the threshold (default: 3 times), providing a different key each time:
vault operator unseal # Enter Unseal Key 1vault operator unseal # Enter Unseal Key 2vault operator unseal # Enter Unseal Key 3Verify the status:
vault statusConfirm that Sealed shows false before proceeding.
5. Log In with the Root Token
vault login# Enter the Initial Root Token from `vault operator init`Once logged in, all subsequent steps — enabling the Database Secrets Engine, configuring MariaDB, creating roles, and generating credentials — are identical to the dev mode workflow below. Just make sure VAULT_ADDR points to http://<VAULT_HOST>:<VAULT_PORT> instead of 127.0.0.1:8200.
Security note: The setup above (
tls_disable = true, single-node manual unseal) is appropriate only for testing and development on trusted/private networks. For production deployments, always enable TLS, consider auto-unseal (e.g., via cloud KMS), and restrict network access to the Vault port.
Step 2: Set Up MariaDB (Docker Compose)
Create a docker-compose.yml file to spin up a MariaDB instance:
services: database: container_name: mariadb image: mariadb:latest restart: always ports: - "<MARIADB_PORT>:3306" environment: - MARIADB_ROOT_PASSWORD=<MARIADB_ROOT_PASSWORD> volumes: - mariadb-data:/var/lib/mysql
volumes: mariadb-data: name: mariadb-dataStart the container:
docker compose up -dVerify it’s running:
docker psOutput:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESde13ac6ba5cb mariadb:latest "docker-entrypoint.s…" 4 seconds ago Up 3 seconds 0.0.0.0:3306->3306/tcp mariadbWith this configuration, MariaDB is accessible at:
| Parameter | Value |
|---|---|
| Host | <MARIADB_HOST> (or 127.0.0.1 if Vault runs on the same machine) |
| Port | <MARIADB_PORT> |
| User | root |
| Password | <MARIADB_ROOT_PASSWORD> |
The root credentials are what Vault uses as its management account — Vault will authenticate as root to create and revoke dynamic user accounts on your behalf.
Step 3: Configure Vault Database Secrets Engine
Before proceeding, make sure that MariaDB at <MARIADB_HOST>:<MARIADB_PORT> is reachable from the machine where Vault is running.
3.1 Enable the Database Secrets Engine
vault secrets enable databaseOutput:
Success! Enabled the database secrets engine at: database/3.2 Configure the Database Connection
Tell Vault how to connect to MariaDB. Vault uses the mysql-database-plugin, which is fully compatible with MariaDB:
vault write database/config/mariadb \ plugin_name=mysql-database-plugin \ connection_url="{{username}}:{{password}}@tcp(<MARIADB_HOST>:<MARIADB_PORT>)/" \ allowed_roles="*" \ username="root" \ password="<MARIADB_ROOT_PASSWORD>"Parameter breakdown:
| Parameter | Purpose |
|---|---|
connection_url | MariaDB address with {{username}} and {{password}} placeholders that Vault fills in automatically |
allowed_roles="*" | Permits all roles defined under this connection to use it. For production, restrict to explicit role names (e.g., "db-admin,db-read-only") |
username / password | The management credentials Vault uses to create and drop dynamic users |
3.3 Create an Admin Role
This role generates users with full ALL PRIVILEGES — suitable for administrative or migration workloads:
vault write database/roles/db-admin \ db_name=mariadb \ creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';" \ creation_statements="GRANT ALL PRIVILEGES ON *.* TO '{{name}}'@'%';" \ revocation_statements="DROP USER '{{name}}'@'%';" \ default_ttl="1h" \ max_ttl="1h"3.4 Create a Read-Only Role
This role generates users with SELECT privileges only — ideal for reporting dashboards, data pipelines, or read replicas:
vault write database/roles/db-read-only \ db_name=mariadb \ creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';" \ creation_statements="GRANT SELECT ON *.* TO '{{name}}'@'%';" \ revocation_statements="DROP USER '{{name}}'@'%';" \ default_ttl="1h" \ max_ttl="1h"Step 4: Generate Dynamic Credentials
With the roles in place, request credentials on demand. You no longer need to know or manage any static passwords.
Admin credentials:
vault read database/creds/db-adminRead-only credentials:
vault read database/creds/db-read-onlyExample output:
Key Value--- -----lease_id database/creds/db-read-only/2f6a614c-4aa2-7b19-24b9-ad944a8d4de6lease_duration 1hlease_renewable truepassword yY-57n3X5UQhxnmFRP3fusername v-root-db-read-onl-crBWVqVh2Hc1Each invocation creates a brand-new MariaDB user with a randomly generated username and password. When the TTL expires, Vault automatically executes the
revocation_statementsto drop that user from MariaDB.
Step 5: Verify the Credentials
Use the freshly generated credentials to authenticate against MariaDB:
mariadb -h <MARIADB_HOST> \ -u v-root-db-read-onl-crBWVqVh2Hc1 \ -pyY-57n3X5UQhxnmFRP3f \ -e "SHOW DATABASES;"Expected behavior:
- Credentials from
db-admincan execute any SQL operation (DDL, DML, etc.) - Credentials from
db-read-only:SELECTqueries succeed;INSERT,DROP, etc., are rejected with an access denied error
If both behave as expected, the Vault and MariaDB integration is working correctly.
Step 6 (Optional): Lease Management & Manual Revocation
List Active Leases
vault list sys/leases/lookup/database/creds/db-read-onlyRevoke a Specific Lease Early
vault lease revoke database/creds/db-read-only/<lease_id>After revocation, Vault immediately executes the DROP USER statement on MariaDB — the credentials are invalidated instantly, even before the TTL would have expired.
Revoke All Leases for a Role
vault lease revoke -prefix database/creds/db-read-only/This is useful in incident response scenarios where you want to instantly invalidate every active credential for a compromised role.
Why This Matters: Security Benefits
| Traditional Approach | Vault Dynamic Secrets |
|---|---|
Static password stored in .env or config | No static password exists anywhere |
| Password shared across multiple services | Each service/request gets a unique credential |
| Manual rotation (easy to forget) | Automatic revocation at TTL expiry |
| Audit trail unclear | Every credential tied to a Vault lease ID |
| Leaked password = permanent breach risk | Leaked credential expires in 1 hour or less |
What’s Next
Once you’ve verified the basic flow, consider exploring these advanced capabilities:
-
Root credential rotation — Let Vault manage and rotate the
rootpassword it uses:Terminal window vault write -force database/rotate-root/mariadbAfter this command, only Vault knows the root password.
-
Static roles — For existing long-lived database users that need periodic password rotation rather than dynamic creation.
-
Vault Agent — A sidecar daemon that automatically fetches and renews credentials, injecting them into your application’s environment or config files with zero code changes.
-
Vault API / SDK — Integrate dynamic credential retrieval directly into your application at startup time using the Vault HTTP API or language-specific SDKs (Go, Python, Java, etc.).
Conclusion
By integrating HashiCorp Vault’s Database Secrets Engine with MariaDB, you fundamentally change how applications access your database:
- No more static passwords — credentials are generated on-demand and never stored in plaintext
- Automatic isolation — every consumer gets a unique credential, making audit logs precise and breach impact minimal
- Zero-config revocation — credentials expire automatically; incident response is a single
vault lease revokecommand away
This pattern is the foundation of secrets management at scale and applies equally well to PostgreSQL, MySQL, Oracle, MongoDB, and other supported database engines.
Reference: Vault MySQL/MariaDB Database Secrets Engine — HashiCorp Docs