Skip to main content

Command Palette

Search for a command to run...

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

Updated
6 min read
M

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:

🪟 Windows users: Use set instead of export 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 the vault-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

  1. Navigate to vault-docker folder and start the containers:

     docker-compose up -d
    
  2. Verify the containers are running:

     docker ps
    

    You should see vault and mysql containers.

  3. 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.

  4. Unseal Vault:

     vault operator unseal <unseal-key>
    

    Use the single unseal key from the init output. Vault is now unsealed and ready.

  5. 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 (or http://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 in vault.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 port 8200 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 the connection_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

Note: For questions or setup help, reach out, comment below or check the Vault documentation.

Getting Started with HashiCorp Vault and Docker Compose: A Beginner’s Guide

Part 3 of 4

A beginner-friendly series guiding you through HashiCorp Vault using Docker Compose. Learn setup, persistent storage, clustering, auto-unseal, and load balancing step by step.

Up next

Getting Started with HashiCorp Vault and Docker Compose (Part 1)

Quick setup for a Vault instance to manage secrets in development mode using Docker Compose