Introduction
HashiCorp Vault is the leading solution for centralizing the management of secrets, tokens, and certificates. In a modern DevOps context, storing plaintext passwords in code or environment variables poses a major risk. This tutorial guides you step by step through deploying Vault, configuring it, and integrating it into an application. You will learn how to create dynamic policies and access secrets securely. The progressive approach takes you from a development environment to a production-ready configuration.
Prerequisites
- Docker and Docker Compose installed
- Basic command-line knowledge
- Basic understanding of TypeScript and Node.js
- Administrator access on your machine
Deployment with Docker Compose
version: '3.8'
services:
vault:
image: hashicorp/vault:1.18
container_name: vault
ports:
- "8200:8200"
environment:
VAULT_DEV_ROOT_TOKEN_ID: "dev-only-token"
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
cap_add:
- IPC_LOCKThis file starts Vault in development mode with a fixed root token. Dev mode is ideal for testing but never for production.
Initialization and Unsealing
#!/bin/bash
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='dev-only-token'
vault status
vault secrets enable -path=secret kv-v2
vault kv put secret/app/config username="admin" password="s3cr3t"Enables the KV v2 engine and stores an initial key-value pair. The dev token provides immediate access without full initialization.
Creating a Policy
path "secret/data/app/config" {
capabilities = ["read"]
}
path "secret/metadata/app/*" {
capabilities = ["list"]
}This policy allows only reading the application's secrets. Limiting permissions is essential to follow the principle of least privilege.
Applying the Policy
vault policy write app-policy app-policy.hcl
vault token create -policy=app-policy -ttl=1hCreates a token with the restricted policy. The application will use this token to read secrets without administrative rights.
Node.js Client to Read Secrets
import * as vault from 'node-vault';
const client = vault({
apiVersion: 'v1',
endpoint: 'http://127.0.0.1:8200',
token: process.env.VAULT_TOKEN
});
export async function getAppConfig() {
const result = await client.read('secret/data/app/config');
return result.data.data;
}The client uses the limited token to retrieve secrets. Always validate the token's presence before any request in production.
Integration into an Express API
import express from 'express';
import { getAppConfig } from './vault-client';
const app = express();
app.get('/config', async (req, res) => {
try {
const config = await getAppConfig();
res.json(config);
} catch (error) {
res.status(500).json({ error: 'Impossible to retrieve secrets' });
}
});
app.listen(3000, () => console.log('Server started'));The API exposes configuration data without ever hard-coding it. Handle errors to prevent leaks of sensitive information.
Best Practices
- Always use short-lived tokens and renew them automatically
- Enable audit logging to track all secret accesses
- Prefer the KV v2 engine for versioning and secure deletion
- Separate environments using namespaces or distinct Vault instances
- Integrate Vault into CI/CD pipelines using dynamic authentication roles
Common Mistakes to Avoid
- Leaving dev mode enabled in production
- Granting overly broad permissions to application tokens
- Forgetting to encrypt data at rest with an external KMS
- Storing the root token in source code or environment variables
Going Further
Explore Kubernetes authentication and dynamic database secrets. Discover our advanced security training.