HashiCorp Vault with Docker Compose: Persistent Storage and Dynamic Secrets (Part 2)
Add persistent data, manual unsealing, and MySQL dynamic secrets to your Vault setup
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.
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 vaulton macOS).A text editor (e.g., VS Code).
Familiarity with Part 1’s setup.
🪟 Windows users: Use
setinstead ofexportfor 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-datavolume.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-dockerfolder and start the containers:docker-compose up -dVerify the containers are running:
docker psYou should see
vaultandmysqlcontainers.Initialize Vault (run once):
export VAULT_ADDR=http://localhost:8200 vault operator init -key-shares=1 -key-threshold=1This 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
initoutput. 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
SELECTpermissions on all databases.Run
vault read database/creds/my-roleagain to generate new credentials.
Step 5: Access Vault
Web UI: Open
http://localhost:8200(orhttp://127.0.0.1:8200if 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-appSecrets persist across container restarts due to the
vault-datavolume.
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-datavolume persists data, but back up the storage directory regularly in production.Enable TLS: In production, set
tls_disable = 0invault.hcland 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 port8200is 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_urlmatches 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.
