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

In [Part 1](hashicorp-vault-docker-compose-part1), 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](https://docs.docker.com/get-docker/).
    
* **Docker Compose** (version 2.0 or later). See [Docker Compose installation](https://docs.docker.com/compose/install/).
    
* **Vault CLI** for CLI access. Download from [releases.hashicorp.com/vault](https://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 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`):

```yaml
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`.

```plaintext
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:
    
    ```bash
    docker-compose up -d
    ```
    
2. Verify the containers are running:
    
    ```bash
    docker ps
    ```
    
    You should see `vault` and `mysql` containers.
    
3. Initialize Vault (run once):
    
    ```bash
    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:
    
    ```bash
    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:
    
    ```bash
    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:

```bash
# 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:
    
    ```bash
    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:

```bash
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](https://discuss.hashicorp.com)!

---

## Resources

* **HashiCorp Vault Documentation**: [vaultproject.io](https://www.vaultproject.io)
    
* **Docker Compose Documentation**: [docs.docker.com/compose](https://docs.docker.com/compose)
    
* **HashiCorp Learn: Database Secrets**: [learn.hashicorp.com/tutorials/vault/database-secrets](https://learn.hashicorp.com/tutorials/vault/database-secrets)
    

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