HashiCorp Vault is the industry standard for secrets management in modern cloud environments. In this article I walk through setting up Vault in a Podman container on macOS step by step including TLS certificates, initialization and secure unseal key management via PowerShell SecretManagement. The setup described here is production-ready.
What is HashiCorp Vault?
Vault is a tool for centrally managing secrets such as API keys, passwords, certificates and tokens. Core functionality:
- Secret storage encrypted, with versioning (KV v2)
- Dynamic secrets temporary credentials for databases, cloud providers, etc.
- Encryption as a Service encrypt data without managing the keys yourself
- MFA and fine-grained policies precise access control per team or application
Prerequisites
- Podman installed and machine running (see my previous article)
- PowerShell 7+
- OpenSSL (
brew install openssl) - Vault CLI (
brew install vault) - PowerShell SecretManagement:
Install-Module Microsoft.PowerShell.SecretManagement
The PowerShell module
The complete setup is implemented as a PowerShell module with dedicated functions. The code is split into logical blocks that you execute step by step. Below we walk through the complete flow.
Step 1: Environment setup , New-VaultProdSetup
The first function creates the directory structure, generates TLS certificates using OpenSSL and writes the Vault configuration.
Directory structure
All Vault files are stored under $HOME/Vault-d2cit/:
~/Vault-d2cit/
├── config/ # vault.hcl and local.json
├── data/ # (unused with file storage)
├── file/ # Vault file storage backend
├── log/ # Vault logs
└── certs/
├── public.crt
└── private.key
Generating TLS certificates
Vault requires TLS in production. We generate a self-signed certificate with OpenSSL and trust it on macOS:
openssl req -newkey rsa:4096 -nodes -sha256 -keyout private.key `
-addext "subjectAltName = DNS:localhost,IP:127.0.0.1" `
-x509 -days 365 -out public.crt `
-subj "/C=NL/ST=Noord-Brabant/L=Breda/O=DevOps/CN=localhost"
# Trust certificate on macOS (requires sudo)
sudo security add-trusted-cert -d -r trustRoot `
-k /Library/Keychains/System.keychain ~/Vault-d2cit/certs/public.crt
Vault configuration (local.json)
{
"disable_mlock": true,
"storage": {"file": {"path": "/vault/file"}},
"listener": [{
"tcp": {
"address": "0.0.0.0:8202",
"tls_cert_file": "/vault/certs/public.crt",
"tls_key_file": "/vault/certs/private.key",
"tls_min_version": "tls12"
}
}],
"ui": true
}
disable_mlock = true is required in a container environment because containers cannot lock memory by default.
Step 2: Start the container — Start-VaultProd
We start the official HashiCorp Vault image with four volume mounts:
podman run -d `
--name Vault-d2cit `
--cap-add=IPC_LOCK `
-v ~/Vault-d2cit/log:/vault/logs `
-v ~/Vault-d2cit/config:/vault/config `
-v ~/Vault-d2cit/certs:/vault/certs:ro `
-v ~/Vault-d2cit/file:/vault/file `
-e SKIP_CHOWN=true `
-e SKIP_SETCAP=true `
-p 8200:8200 `
hashicorp/vault:latest server
Notable details:
--cap-add=IPC_LOCK— allows Vault to lock memory:roon the certs mount — read-only for extra securitySKIP_CHOWN=trueandSKIP_SETCAP=true— prevents permission errors in rootless Podman- Port 8200 on the host is mapped to 8200 in the container (Vault default)
After starting, wait 15 seconds before proceeding:
Start-Sleep -Seconds 15
podman logs Vault-d2cit
Step 3: Initialize Vault — Invoke-VaultInit
A fresh Vault instance needs to be initialized. This generates the unseal keys and root token. This is done only once.
$env:VAULT_ADDR = "https://127.0.0.1:8200"
$env:VAULT_SKIP_VERIFY = "true"
# Initialize
$initOutput = vault operator init | Tee-Object ~/Vault-d2cit/init-keys.txt
The output contains 5 unseal keys and 1 root token. We store these securely via PowerShell SecretManagement — never in plaintext:
$key1 = ($initOutput | Select-String "Unseal Key 1").Line.Split()[-1]
$rootToken = ($initOutput | Select-String "Root Token").Line.Split()[-1]
Set-Secret -Name "Vault-Unseal-Key1" -Secret $key1
Set-Secret -Name "Vault-Root-Token" -Secret $rootToken
# ... repeat for Key2 through Key5
Step 4: Unseal Vault — Invoke-VaultUnseal
After every (re)start Vault is sealed. You need 3 of the 5 unseal keys to unseal it (Shamir’s Secret Sharing). Keys are retrieved from SecretManagement never hardcoded:
$key1 = Get-Secret -Name "Vault-Unseal-Key1" -AsPlainText
$key2 = Get-Secret -Name "Vault-Unseal-Key2" -AsPlainText
$key3 = Get-Secret -Name "Vault-Unseal-Key3" -AsPlainText
vault operator unseal $key1
vault operator unseal $key2
vault operator unseal $key3
Step 5: Check status
# Via Vault CLI
vault status
# Via REST API
$health = Invoke-RestMethod "https://127.0.0.1:8200/v1/sys/health" -SkipCertificateCheck
$health | Select-Object initialized, sealed, version
# Container status
podman ps --filter name=Vault-d2cit
The Vault UI is available at https://127.0.0.1:8200. Log in with the root token from SecretManagement:
Get-Secret -Name "Vault-Root-Token" -AsPlainText
Optional: Create an admin user
The root token is only intended for initial setup. Create an admin account using userpass authentication:
# Enable userpass auth
vault auth enable userpass
# Admin policy
$policy = 'path "*" { capabilities = ["create","read","update","delete","list","sudo"] }'
$policy | vault policy write admin -
# Create user
vault write auth/userpass/users/mark `
password="YourPassword" `
policies="default,admin"
Optional: Set up TOTP MFA
For extra security, add TOTP-based MFA. After creating the MFA method, link it to a user and scan the QR code with an authenticator app (e.g. 1Password or Google Authenticator):
# Create MFA method
vault write identity/mfa/method/totp `
issuer="Vault" period=30 algorithm="SHA1" digits=6 generate=true
# Enforce MFA for all userpass logins
vault write auth/userpass/config mfa_methods_required=<METHOD_ID>
Conclusion
With this setup you have a production-ready HashiCorp Vault instance running in a rootless Podman container on macOS. The combination of TLS, PowerShell SecretManagement for unseal keys and a dedicated admin account (instead of the root token) provides a solid security foundation.
The complete PowerShell module with all functions (New-VaultProdSetup, Start-VaultProd, Invoke-VaultInit, Invoke-VaultUnseal, New-VaultUserWithMFA etc.) is available on request.