Secure Python Secrets with HashiCorp Vault
Learn how to centralize and protect API keys, database passwords, and other sensitive data in Python applications using HashiCorp Vault and the hvac library, with step-by-step setup and production best practices.
Keep Your Python Secrets Safe with HashiCorp Vault
Hardcoding API keys, database passwords, and other secrets in your Python code is a bad habit that can come back to bite you. I've seen too many projects accidentally expose sensitive credentials in version control or logs. Let me show you a better way using HashiCorp Vault.
Why Vault matters
Think of Vault as a secure digital vault where you store all your sensitive data. Instead of scattering secrets across config files, environment variables, or—God forbid—hardcoded strings, you centralize them in one encrypted location. Python apps can then request exactly what they need, when they need it.
Setting up Vault locally
First, let's get Vault running on your machine for testing. Download it from the official site, then start a development server:
vault server -dev
You'll get a root token and an address like http://127.0.0.1:8200. Keep these handy.
Writing secrets to Vault
Let's add some sample configuration data:
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='your-root-token'
vault kv put secret/python-skillset \
db_host='localhost' \
db_port='5432' \
api_key='sk-abc123def456'
Now those values are encrypted and accessible only with the right token.
Reading secrets from Python
Here's where PythonSkillset readers will appreciate the clean approach. Install the hvac library:
pip install hvac
Now create a simple config reader:
import hvac
class VaultConfigReader:
def __init__(self, vault_addr, vault_token):
self.client = hvac.Client(
url=vault_addr,
token=vault_token
)
def get_secret(self, path, key=None):
response = self.client.secrets.kv.v2.read_secret_version(
path=path
)
data = response['data']['data']
return data.get(key) if key else data
# Usage in Pythonskillset application
config = VaultConfigReader('http://127.0.0.1:8200', os.environ['VAULT_TOKEN'])
db_config = config.get_secret('python-skillset', 'db_host')
print(f"Connecting to database at {db_config}")
A real-world Phoenix example
Let's say you're building an e-commerce application called PhoenixShop. Instead of storing the Stripe API key in a config file, you'd do this:
import os
import hvac
from flask import Flask
app = Flask(__name__)
def load_sensitive_config():
vault_client = hvac.Client(
url='https://vault.phoenixshop.internal',
token=os.environ['VAULT_TOKEN']
)
secrets = vault_client.secrets.kv.v2.read_secret_version(
path='phoenixshop/production'
)['data']['data']
app.config['STRIPE_SECRET_KEY'] = secrets['stripe_api_key']
app.config['DATABASE_URL'] = secrets['db_connection_string']
load_sensitive_config()
No more worrying about accidentally committing secrets to GitHub.
Best practices for production
In development, using the root token is fine. But for production, you'll want to:
- Use AppRole authentication instead of tokens
- Set short TTLs on credentials
- Enable audit logging to track who accessed what
- Use dynamic secrets for databases when possible
Here's how AppRole works in practice:
# Production-grade setup
vault_client = hvac.Client(url='https://vault.pythonskillset.com')
# Login with role ID and secret ID
vault_client.auth.approle.login(
role_id='your-role-id',
secret_id='your-secret-id'
)
# Now requests are authenticated with limited permissions
db_creds = vault_client.secrets.database.generate_credentials(
mount_point='database',
name='readonly-role'
)
The key takeaway
Your application configuration doesn't need to be a security risk. By centralizing secrets in Vault, you make your Python apps more secure, easier to audit, and simpler to manage across different environments. Your future self—and your security team—will thank you.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.