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
  • :ro on the certs mount — read-only for extra security
  • SKIP_CHOWN=true and SKIP_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.