HashiCorp Vault with Docker Compose: Persistent Storage and Dynamic Secrets (Part 2)

In Part 1, you set up a simple HashiCorp Vault instance using Docker Compose in development mode - perfect for local testing and quick prototyping. It stores everything in memory, which is ideal for developers who want to explore Vault features fast without setup overhead.
⚠️ In dev mode, all secrets are lost when the container stops.
In this second part of the series, we’ll take a small step toward a more realistic setup:
Enable persistent storage so secrets survive restarts,
Use manual unsealing (a core Vault security feature),
Connect Vault to a MySQL database to issue dynamic secrets - temporary credentials that expire automatically.
This guide is still beginner-friendly and assumes no prior production Vault experience. You’ll build confidence by running everything locally with Docker, just like in Part 1, but with added layers of realism.
🧠 Why dynamic secrets?
Unlike static secrets, dynamic secrets are generated on demand. When an app or user needs access to a database, Vault can create a temporary username and password with limited permissions that self-destruct after an hour or so. This removes the need to manage and rotate credentials manually.
🔁 Quick Recap of Part 1
Previously, we:
Ran Vault in development mode using Docker Compose.
Stored secrets in memory (they were lost after each restart).
Explored the UI and created static secrets manually.
Now, we're building a persistent, more production-like setup with real secret automation
Prerequisites
Before starting, ensure you have:
Docker (version 20.10 or later). Install from Docker’s guide.
Docker Compose (version 2.0 or later). See Docker Compose installation.
Vault CLI for CLI access. Download from releases.hashicorp.com/vault or use a package manager (e.g.,
brew install vault
on macOS).A text editor (e.g., VS Code).
Familiarity with Part 1’s setup.
🪟 Windows users: Use
set
instead ofexport
for environment variables.
Setting Up Vault with Persistent Storage
Step 1: Create the Docker Compose File
Create a file named docker-compose.yml
in a new directory (e.g., vault-docker
):
services:
vault:
image: hashicorp/vault:latest
ports:
- "8200:8200"
environment:
- VAULT_ADDR=http://0.0.0.0:8200
cap_add:
- IPC_LOCK
volumes:
- vault-data:/vault/data
- ./vault-config:/vault/config
command: vault server -config=/vault/config/vault.hcl
depends_on:
- mysql
mysql:
image: mysql:8
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
ports:
- "3306:3306"
volumes:
vault-data:
Key Notes:
vault-data
: A Docker volume for persistent storage, keeping secrets between restarts.vault-config
: Maps a local directory for the Vault configuration file.mysql
: A MySQL container for dynamic secrets, with a root password (rootpass
).depends_on
: Ensures MySQL starts before Vault for dynamic secrets setup.
Step 2: Create the Vault Configuration
Unlike dev mode, we now provide an actual configuration file (vault.hcl) that Vault reads when starting in server mode.
Create a vault-config
directory in vault-docker
and add a file named vault.hcl
.
storage "file" {
path = "/vault/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
ui = true
Key Notes:
storage "file"
: Stores secrets in/vault/data
, mapped to thevault-data
volume.tls_disable = 1
: Disables TLS for simplicity; in production, enable TLS with certificates.ui = true
: Enables the Vault web UI.
Step 3: Start and Initialize Vault
Navigate to
vault-docker
folder and start the containers:docker-compose up -d
Verify the containers are running:
docker ps
You should see
vault
andmysql
containers.Initialize Vault (run once):
export VAULT_ADDR=http://localhost:8200 vault operator init -key-shares=1 -key-threshold=1
This outputs one unseal key and a root token. Save them securely (e.g., in a password manager). Normally, Vault generates multiple keys (e.g., five, with a threshold of three) for added security, but we’re using a single key for simplicity. Do not use a single key in production, as it reduces security.
Unseal Vault:
vault operator unseal <unseal-key>
Use the single unseal key from the
init
output. Vault is now unsealed and ready.Log in with the root token:
export VAULT_TOKEN=<root-token> vault login
Step 4: Set Up MySQL Dynamic Secrets
Vault can generate temporary MySQL credentials that expire after a set time (e.g., 1 hour). Let’s configure this:
# Enable the database secrets engine
vault secrets enable database
# Configure MySQL connection so Vault can communicate with MySQL
vault write database/config/my-mysql \
plugin_name=mysql-database-plugin \
connection_url="root:rootpass@tcp(mysql:3306)/" \
allowed_roles="my-role"
# Create a role that controls how dynamic users are created
vault write database/roles/my-role \
db_name=my-mysql \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON *.* TO '{{name}}'@'%';" \
default_ttl="1h" \
max_ttl="24h"
# Test: generate credentials on the fly!
vault read database/creds/my-role
Key Notes:
This creates temporary MySQL credentials that expire after 1 hour (
default_ttl="1h"
).The credentials grant
SELECT
permissions on all databases.Run
vault read database/creds/my-role
again to generate new credentials.
Step 5: Access Vault
Web UI: Open
http://localhost:8200
(orhttp://127.0.0.1:8200
if localhost is blocked). Log in with the root token. Navigate to “Secrets” to view or create secrets.CLI: Check Vault status or store a secret:
vault status vault kv put secret/my-app db_password=supersecret vault kv get secret/my-app
Secrets persist across container restarts due to the
vault-data
volume.
Step 6: Stop Vault
To stop the containers:
docker-compose down
On restart, you’ll need to unseal Vault again using the single unseal key.
Best Practices
Secure Unseal Key and Root Token: Store the unseal key and root token in a secure location (e.g., password manager or HSM). Never store them in plain text or version control. In production, use multiple keys (e.g., three out of five) for better security.
Use Multiple Unseal Keys: In production, use e.g., 3-of-5 shares.
Limit Root Token Use: Use the root token only for setup. Create non-root users (see Part 1 experiments) for regular access.
Backup Storage: The
vault-data
volume persists data, but back up the storage directory regularly in production.Enable TLS: In production, set
tls_disable = 0
invault.hcl
and provide TLS certificates.Dynamic Secrets Are Short-Lived: Vault-created DB credentials only live for the TTL you set. This reduces risk if credentials leak or are forgotten.
Troubleshooting
Vault Not Accessible: Ensure containers are running (
docker ps
) and port8200
is free (lsof -i :8200
). Check firewall settings.Unseal Fails: Verify the unseal key is correct. If lost, re-run
vault operator init -key-shares=1 -key-threshold=1
(this resets Vault).MySQL Connection Error: Confirm MySQL is running (
docker logs mysql
) and theconnection_url
matches the MySQL container’s credentials.
What’s Next?
You’ve set up Vault with persistent storage and dynamic MySQL credentials! This setup is closer to production but still runs on a single node. In Part 3, we’ll create a three-node Vault cluster for high availability. Try these experiments:
Store a secret in the web UI and retrieve it via CLI.
Generate new MySQL credentials and test them with a MySQL client.
Explore token time-to-live (TTL) behavior for dynamic secrets.
Share your progress in the comments or join the HashiCorp Community Forum!
Resources
HashiCorp Vault Documentation: vaultproject.io
Docker Compose Documentation: docs.docker.com/compose
HashiCorp Learn: Database Secrets: learn.hashicorp.com/tutorials/vault/database-secrets
Note: For questions or setup help, reach out, comment below or check the Vault documentation.
Subscribe to my newsletter
Read articles from Miroslav Milak directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Miroslav Milak
Miroslav Milak
HashiCorp Vault SME Certified | Passionate about secure secrets management and cloud infrastructure. Sharing insights, tutorials, and best practices on HashiNode to help engineers build resilient, scalable systems. Advocate for DevSecOps and cutting-edge security solutions.