Skip to content
Back to Blog
DevOps

HashiCorp Vault: Secrets Management for Infrastructure

Deploy and use HashiCorp Vault for dynamic secrets, PKI, database credentials, and Kubernetes integration in production.

Nov 2025
17 min read

Introduction

HashiCorp Vault is a secrets management tool that provides secure storage and access control for sensitive data like API keys, passwords, and certificates. Without Vault, secrets end up hardcoded in code, stored in environment variables, or scattered across configuration files — any of which are security risks. This guide teaches you to deploy Vault, store secrets, and integrate it with your applications.

Why Vault?

  • Dynamic secrets: Vault can generate database credentials on-demand (auto-expiring)
  • Secret leasing: Secrets have TTLs and are automatically revoked
  • Audit logging: Every secret access is logged
  • Fine-grained access: Policies control exactly what each app can read
  • Encryption as a service: Encrypt data without managing keys yourself

Installation and Initialization

BASH
# Install Vault (Linux)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [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 update && sudo apt install vault

# Docker deployment
docker run -d   --name vault   --cap-add IPC_LOCK   -p 8200:8200   -e VAULT_ADDR=http://0.0.0.0:8200   -v vault-data:/vault/data   -v ./vault-config.hcl:/vault/config/vault.hcl   hashicorp/vault:latest server
HCL
# vault-config.hcl
ui = true

storage "file" {
  path = "/vault/data"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = 1    # Enable TLS in production!
}

api_addr = "http://vault.company.com:8200"
BASH
# Initialize Vault (run once)
export VAULT_ADDR='http://localhost:8200'
vault operator init -key-shares=5 -key-threshold=3

# This outputs:
# Unseal Key 1: XXXX
# Unseal Key 2: XXXX
# ...
# Initial Root Token: hvs.XXXX

# SAVE THESE SECURELY! You need 3 of 5 keys to unseal

# Unseal Vault (need 3 keys)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>

# Verify
vault status

# Login with root token
vault login hvs.XXXX

Storing and Retrieving Secrets

BASH
# Enable KV secrets engine
vault secrets enable -path=secret kv-v2

# Store a secret
vault kv put secret/myapp/database   username=appuser   password=Str0ngP@ss!   host=db.company.com

# Read a secret
vault kv get secret/myapp/database
# Key      Value
# ---      -----
# username appuser
# password Str0ngP@ss!

# Get specific field
vault kv get -field=password secret/myapp/database

# Update (add new version)
vault kv patch secret/myapp/database password=NewP@ss!

# List versions
vault kv metadata get secret/myapp/database

# Read specific version
vault kv get -version=1 secret/myapp/database

Access Policies

HCL
# policies/myapp-policy.hcl
# Allow myapp to read its own secrets only
path "secret/data/myapp/*" {
  capabilities = ["read", "list"]
}

# Allow renewing its own token
path "auth/token/renew-self" {
  capabilities = ["update"]
}

# Deny everything else (implicit)
BASH
# Create policy
vault policy write myapp policies/myapp-policy.hcl

# Create token with policy
vault token create -policy=myapp -ttl=1h -renewable=true

# Or use AppRole auth (better for applications)
vault auth enable approle
vault write auth/approle/role/myapp   token_policies=myapp   token_ttl=1h   token_max_ttl=4h

# Get role credentials
vault read auth/approle/role/myapp/role-id
vault write -f auth/approle/role/myapp/secret-id

# App logs in with these credentials
vault write auth/approle/login   role_id=<role-id>   secret_id=<secret-id>

Dynamic Database Secrets

The killer feature: Vault generates temporary DB credentials that auto-expire:

BASH
# Enable database secrets engine
vault secrets enable database

# Configure PostgreSQL connection
vault write database/config/myapp-db   plugin_name=postgresql-database-plugin   allowed_roles="myapp-role"   connection_url="postgresql://vault_admin:vaultpass@db.company.com:5432/appdb"   username="vault_admin"   password="vaultpass"

# Define role that creates short-lived credentials
vault write database/roles/myapp-role   db_name=myapp-db   creation_statements="CREATE ROLE '{{name}}' WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO '{{name}}';"   default_ttl="1h"   max_ttl="24h"

# Request dynamic credentials (each request gets unique credentials!)
vault read database/creds/myapp-role
# Key                Value
# ---                -----
# lease_duration     1h
# username           v-myapp-AbCdEfGh1234
# password           A1B2C3D4E5F6G7H8I9J0

Application Integration

PYTHON
#!/usr/bin/env python3
# vault_client.py - Fetch secrets from Vault at startup
import hvac
import os
import time

class VaultSecrets:
    def __init__(self, vault_addr, role_id, secret_id):
        self.client = hvac.Client(url=vault_addr)
        self.client.auth.approle.login(
            role_id=role_id,
            secret_id=secret_id
        )

    def get_secret(self, path, key=None):
        data = self.client.secrets.kv.v2.read_secret_version(
            path=path,
            mount_point='secret'
        )
        if key:
            return data['data']['data'][key]
        return data['data']['data']

    def get_database_credentials(self, role):
        '''Get dynamic database credentials.'''
        creds = self.client.secrets.database.generate_credentials(name=role)
        return {
            'username': creds['data']['username'],
            'password': creds['data']['password'],
            'lease_id': creds['lease_id'],
            'lease_duration': creds['lease_duration']
        }

    def renew_token(self):
        '''Renew Vault token before expiry.'''
        self.client.auth.token.renew_self()

# Usage in application
vault = VaultSecrets(
    vault_addr=os.environ['VAULT_ADDR'],
    role_id=os.environ['VAULT_ROLE_ID'],
    secret_id=os.environ['VAULT_SECRET_ID']
)

# Get static secret
api_key = vault.get_secret('myapp/external-api', 'api_key')

# Get dynamic database credentials
db_creds = vault.get_database_credentials('myapp-role')
db_url = f"postgresql://{db_creds['username']}:{db_creds['password']}@db:5432/appdb"

Kubernetes Integration with Vault Agent

YAML
# vault-agent-config.hcl (as ConfigMap)
auto_auth {
  method "kubernetes" {
    mount_path = "auth/kubernetes"
    config = {
      role = "myapp"
    }
  }

  sink "file" {
    config = {
      path = "/home/vault/.vault-token"
    }
  }
}

template {
  source      = "/vault/templates/config.tmpl"
  destination = "/vault/secrets/config.env"
  perms       = 0400
}
TEXT
# config.tmpl
{{ with secret "secret/data/myapp/config" }}
DATABASE_URL=postgresql://{{ .Data.data.db_user }}:{{ .Data.data.db_pass }}@db:5432/app
API_KEY={{ .Data.data.api_key }}
{{ end }}
YAML
# Pod spec with Vault Agent sidecar
spec:
  serviceAccountName: myapp
  initContainers:
    - name: vault-agent
      image: vault:latest
      command: ["vault", "agent", "-config=/vault/config/vault-agent.hcl", "-exit-after-auth"]
      volumeMounts:
        - name: vault-config
          mountPath: /vault/config
        - name: vault-secrets
          mountPath: /vault/secrets
  containers:
    - name: app
      env:
        - name: CONFIG_PATH
          value: /vault/secrets/config.env
      volumeMounts:
        - name: vault-secrets
          mountPath: /vault/secrets

Vault transforms secret management from a scattered, insecure practice to a centralized, audited system. Start with static KV secrets, then graduate to dynamic database credentials — the productivity and security gains are immediate.