Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Secrets Management with HashiCorp Vault

By Kokil Thapa | Last reviewed: September 2026

Hard-coded API keys in .env files and plaintext credentials in Git repos cause most production breaches I see on client projects. Secrets Management with HashiCorp Vault gives you a single control plane for passwords, tokens, certificates, and encryption keys. Applications fetch what they need at runtime instead of storing long-lived secrets on disk. This guide covers a practical Vault setup for teams running Linux system administration, Laravel apps, and CI/CD pipelines in 2026.

What is Secrets Management with HashiCorp Vault and why does it matter?

Vault is an identity-aware secrets store from HashiCorp. It encrypts data at rest, controls who can read or write each path, and logs every access attempt. You stop copying the same database password into five servers and three developer laptops.

On production Laravel applications I maintain, the pattern is familiar. A payment gateway key lives in .env on the web server. A cron job on another host uses a copy. A contractor gets a third copy over Slack. Rotation means editing multiple files and hoping nothing breaks at 2 a.m.

Vault replaces that sprawl with one source of truth. Your app authenticates, receives a token with limited scope, reads the secret, and the token expires. If a laptop is lost, you revoke one policy instead of rotating every credential manually.

Vault Secrets ArchitectureLaravel AppAppRole authCI RunnerJWT authCron WorkerToken authHashiCorp VaultPolicies + AuditDynamic SecretsMySQL 9.7Dynamic credsRedis 8.10Encrypted KV
Secrets Management with HashiCorp Vault centralizes credentials from Laravel apps, CI runners, and workers into one audited control plane.

Vault supports several secret engines. The KV (key-value) engine stores static secrets like SMTP passwords. The database engine creates temporary MySQL or PostgreSQL users with a TTL you define. The PKI engine issues TLS certificates on demand. Transit encrypts data without exposing the master key to your application code.

For enterprise application development teams in Nepal and abroad, the audit log alone justifies the effort. Every read and write is recorded with the caller identity, IP, and timestamp. That matters when you handle client documents on legal-tech portals or payment credentials on eCommerce sites.

Core concepts you must understand first

  • Mount path: Each engine lives at a path like secret/ or database/.
  • Policy: HCL rules that grant read, write, or list on specific paths.
  • Auth method: How a client proves identity—AppRole, JWT, userpass, or cloud IAM.
  • Token: Short-lived credential Vault returns after successful auth.
  • Seal: Encryption barrier; unseal keys or auto-unseal via cloud KMS opens it.

How do you install and configure HashiCorp Vault for production?

Start with a dedicated server or a three-node cluster for high availability. Vault runs fine on Ubuntu 24.04 alongside your existing stack. Do not co-locate it on the same VM as your public web app.

Install from the official HashiCorp repository. Pin a version in your deployment notes so upgrades are deliberate, not accidental package updates.

# Ubuntu 24.04 — add HashiCorp repo and install Vault
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

# Verify binary
vault version

Production server configuration

Create /etc/vault.d/vault.hcl with TLS enabled from day one. Self-signed certs work for internal traffic. Use Let's Encrypt or a proper CA for anything crossing networks you do not fully control.

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-1"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/etc/vault.d/tls/vault.crt"
  tls_key_file  = "/etc/vault.d/tls/vault.key"
}

api_addr     = "https://vault.example.com:8200"
cluster_addr = "https://vault.example.com:8201"
ui           = true
disable_mlock = true

Initialize once, save the unseal keys and root token offline, then unseal each node. For production, enable auto-unseal via cloud KMS so a reboot does not require three people with key shards.

  1. Run vault operator init -key-shares=5 -key-threshold=3 on the first node.
  2. Store unseal keys in separate physical or password-manager locations.
  3. Unseal with vault operator unseal until sealed status is false.
  4. Enable audit logging to a append-only file or syslog sink immediately.
  5. Revoke or rotate the root token after creating an admin policy.

I have seen teams skip audit logging during the "pilot phase" and never turn it on. Treat audit as mandatory from the first secret you store. The same applies to secrets scanning in Git and CI—both layers catch different failure modes.

Vault Auth and Read Flow1. App Login2. Auth Check3. Get Token4. Policy OKRead SecretKV or DynamicAudit LogWho / When / PathToken TTLAuto ExpireDenied: policy mismatch returns 403, logged, no secret leakedPrinciple of least privilege on every path
Every Secrets Management with HashiCorp Vault read passes through authentication, policy enforcement, and audit logging before data leaves the store.

How do you store, rotate, and retrieve secrets in Vault?

Enable the KV v2 engine at a path your team owns. Version 2 adds check-and-set, soft delete, and metadata—use it unless you have a legacy reason not to.

# Enable KV v2 at secret/
vault secrets enable -path=secret kv-v2

# Write a secret
vault kv put secret/laravel/production \
  DB_PASSWORD='strong-random-value' \
  STRIPE_SECRET='sk_live_...'

# Read it back
vault kv get -format=json secret/laravel/production

Writing policies that follow least privilege

Never give an application broad secret/* read access. Scope each policy to one environment and one service.

# /etc/vault.d/policies/laravel-production.hcl
path "secret/data/laravel/production" {
  capabilities = ["read"]
}

path "database/creds/laravel-readonly" {
  capabilities = ["read"]
}

Apply the policy and bind it to an AppRole your Laravel app uses at boot:

vault policy write laravel-production /etc/vault.d/policies/laravel-production.hcl

vault auth enable approle
vault write auth/approle/role/laravel-production \
  token_policies="laravel-production" \
  token_ttl=1h \
  token_max_ttl=4h

Dynamic database credentials

Static database passwords are the biggest win Vault offers for Laravel teams on MySQL 9.7 or PostgreSQL 18. Vault creates a user, grants the role you define, and deletes the user when the TTL expires.

vault secrets enable database

vault write database/config/mysql-prod \
  plugin_name=mysql-database-plugin \
  connection_url="{{username}}:{{password}}@tcp(127.0.0.1:3306)/" \
  allowed_roles="laravel-readonly" \
  username="vault_admin" \
  password="admin-bootstrap-only"

vault write database/roles/laravel-readonly \
  db_name=mysql-prod \
  creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON app_db.* TO '{{name}}'@'%';" \
  default_ttl="1h" \
  max_ttl="24h"

Your app calls vault read database/creds/laravel-readonly at request time or caches credentials until five minutes before expiry. Read the dedicated guide on Vault dynamic secrets for databases for rotation edge cases.

For encryption without storing keys in PHP, enable the Transit engine. Laravel encrypts a payload via Vault's API and stores only ciphertext in the database. That pattern works well for PII fields on client portals like those described in our Mijar Law Associates portfolio case.

How do Laravel apps and CI/CD pipelines integrate with Vault?

Laravel 12 and 13 still read .env by default. Vault does not replace .env overnight. You migrate high-risk values first: payment keys, SMS API tokens, and database passwords.

Vault Agent runs beside your app. It authenticates via AppRole, renders secrets into a file, and renews the token automatically. Laravel reads a generated .env.vault that never enters Git.

# /etc/vault.d/agent-laravel.hcl
pid_file = "/tmp/vault-agent.pid"

auto_auth {
  method {
    type = "approle"
    config = {
      role_id_file_path   = "/etc/vault/role-id"
      secret_id_file_path = "/etc/vault/secret-id"
    }
  }
}

template {
  source      = "/etc/vault/templates/laravel.env.tpl"
  destination = "/var/www/app/shared/.env.vault"
  command     = "systemctl reload php8.3-fpm"
}

The template file maps Vault paths to Laravel env keys:

{{ with secret "secret/data/laravel/production" }}
DB_PASSWORD={{ .Data.data.DB_PASSWORD }}
STRIPE_SECRET={{ .Data.data.STRIPE_SECRET }}
{{ end }}

On Deployer 7 releases I use for sister legal-tech sites, the shared directory persists across symlink swaps. Vault Agent writes there; each new release inherits the same secrets file without manual copy steps. See CI/CD secrets management best practices for pipeline-specific patterns.

Option B: Direct API call at bootstrap

For queue workers or one-off Artisan commands, authenticate once in a service provider and bind values into the config cache:

/* app/Providers/VaultServiceProvider.php — simplified */
$response = Http::post("{$vaultAddr}/v1/auth/approle/login", [
    'role_id'   => config('vault.role_id'),
    'secret_id' => config('vault.secret_id'),
]);
$token = $response->json('auth.client_token');

$secret = Http::withToken($token)
    ->get("{$vaultAddr}/v1/secret/data/laravel/production")
    ->json('data.data');

config(['database.connections.mysql.password' => $secret['DB_PASSWORD']]);

Cache the token until renewal is needed. Do not call Vault on every HTTP request—that adds latency and audit noise.

CI/CD integration

GitLab CI can authenticate with JWT/OIDC if your Vault trust is configured. The pipeline receives a short-lived token, reads deployment keys, and never stores them in CI variables long term.

# .gitlab-ci.yml excerpt
deploy:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://vault.example.com
  script:
    - export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=gitlab-deploy jwt=$VAULT_ID_TOKEN)
    - vault kv get -field=SSH_KEY secret/deploy/production > deploy_key
    - dep deploy production

Pair this with Ansible Vault for secrets only where Ansible still manages host-level config. Vault owns runtime secrets; Ansible Vault can encrypt inventory vars that bootstrap Vault itself—a narrow, intentional overlap.

Static .env vs VaultStatic .env Files• Copied to many servers• Long-lived passwords• No audit trail• Slack / email rotation• Survives in backups• One leak = full accessHashiCorp Vault• Single source of truth• TTL on every credential• Full audit log• Policy-based access• Dynamic DB users• Instant revocationMigration is incremental — start with payment and DB secrets
Secrets Management with HashiCorp Vault eliminates static credential sprawl that static environment files create across servers and developer machines.

How does HashiCorp Vault compare to AWS Secrets Manager and Ansible Vault?

Teams often ask which tool to adopt first. The answer depends on where your apps run and who operates the infrastructure.

CriteriaHashiCorp VaultAWS Secrets ManagerAnsible Vault
Best forMulti-cloud, on-prem, KubernetesAWS-native workloads onlyEncrypting Ansible vars at rest
Dynamic DB credsYes — MySQL, PostgreSQL, MongoDBYes — RDS integrationNo — static encryption only
Audit loggingBuilt-in, exportableCloudTrail integrationNone by default
Self-hosted optionYes — full controlNo — managed AWS serviceYes — local encrypt/decrypt
Operational costServer + HA cluster (~Rs 8,000–15,000/mo VPS, ~USD 60–110)Per-secret monthly feeFree — CLI only
Laravel on Ubuntu VPSExcellent fitRequires AWS SDK + IAMDeploy-time only, not runtime

If everything already lives in AWS, AWS Secrets Manager is simpler to adopt. If you run Laravel on Ubuntu VPS instances—the setup I use for most Nepal client projects—Vault on the same infrastructure or a adjacent management subnet is the natural choice.

Ansible Vault encrypts files in your repo. It does not serve secrets to a running PHP-FPM process. Use both: Ansible Vault for bootstrap secrets that install Vault itself; Vault for everything the application needs at runtime.

For Kubernetes workloads, pair Vault with the External Secrets Operator so pods mount secrets as native Kubernetes secrets without hard-coding Vault tokens in manifests. Read Kubernetes secrets management done right for the full picture.

Common production mistakes

  • Running a single unsealed node with no backup of the raft storage path.
  • Using the root token in CI pipelines instead of scoped roles.
  • Granting sudo policies that include secret/* read on all paths.
  • Storing unseal keys in the same password manager as application passwords.
  • Skipping TLS because "it is internal traffic only."

Generate strong bootstrap passwords with a local password generator during init. Never reuse a password you can remember—that defeats the purpose of a secrets store.

Production HA TopologyLoad Balancer TLSVault Node 1Raft leaderVault Node 2Raft followerVault Node 3Raft followerAuto-Unseal via Cloud KMSNo manual key shards on rebootGotcha: never run production Vault without HA and automated backups
A three-node Raft cluster behind a TLS load balancer is the baseline for production Secrets Management with HashiCorp Vault.

Multi-cloud teams should read multi-cloud secrets management before picking a primary store. Vault often acts as the neutral layer above vendor-specific services.

Protecting secrets in AI integrations is a growing concern. If your Laravel app sends prompts to external LLM APIs, treat those keys like payment credentials. The guide on protecting PII and secrets in LLM apps covers redaction patterns that complement Vault's access controls.

For ongoing operations, fold Vault health checks into your support and maintenance runbook: sealed status, raft peer count, audit device errors, and certificate expiry on the listener.

The official HashiCorp Vault tutorials remain the best reference for engine-specific configuration. Cross-check security assumptions against the OWASP Secrets Management project checklist before go-live.

Key Takeaways

  • Enable KV v2, database, and audit logging before storing your first production secret.
  • Scope policies per app and environment—never grant blanket secret/* read access.
  • Use AppRole or JWT auth for Laravel and CI; rotate role/secret IDs on a schedule.
  • Prefer dynamic database credentials with one-hour TTL over static passwords in .env.
  • Run a three-node Raft cluster with auto-unseal and tested backups, not a single dev-mode instance.
  • Migrate incrementally: payment keys and database passwords first, then SMTP and third-party API tokens.

People Also Ask

Is HashiCorp Vault free for production use?

Vault Community Edition is free and open source under the BSL license. It includes KV, database, PKI, Transit, and AppRole engines sufficient for most Laravel VPS deployments. HashiCorp sells Enterprise for HSM integration, performance replication, and namespaces. Start with Community; upgrade only when a specific Enterprise feature blocks you.

Can Vault replace my Laravel .env file completely?

Not on day one. Laravel expects environment variables at bootstrap. Vault Agent can render a local env file or you can inject config in a service provider. Low-risk values like APP_DEBUG=false can stay in static env files. Move payment keys, database passwords, and signing secrets to Vault first.

How do you back up HashiCorp Vault data?

Back up the raft storage directory with filesystem snapshots while Vault is running—raft supports consistent snapshots via vault operator raft snapshot save. Store snapshots encrypted off-site. Test restore on a staging cluster quarterly. Backing up only the config files without raft data loses every secret.

What happens if Vault goes down?

Applications with cached credentials keep running until TTL expiry. Plan for Vault downtime by setting reasonable token and dynamic credential TTLs, running HA with three nodes, and monitoring sealed state. A sealed cluster rejects all reads—treat unseal failures as a P1 incident with the same urgency as database outage.

Ship Secrets Management with HashiCorp Vault the right way

Static credentials in Git and shared .env files fail quietly until they fail loudly. Secrets Management with HashiCorp Vault gives you audited, revocable, short-lived access that scales from a single Laravel VPS to a multi-service cluster. Start with one AppRole, one KV path, and one dynamic database role. Expand from there as your custom software footprint grows.

If you want help designing Vault policies, wiring Deployer 7 releases, or migrating payment keys off plaintext env files, contact us for a scoped review. You can also browse the about me page for background on production deployments across Nepal legal-tech and eCommerce platforms, or explore related posts on the blog for deeper CI and infrastructure guides.

Frequently Asked Questions

HashiCorp Vault is an identity-aware secrets store that encrypts credentials at rest, controls who can read or write each path, and logs every access attempt. Applications authenticate at runtime, receive short-lived scoped tokens, and fetch passwords, API keys, certificates, or encryption keys instead of relying on static .env files copied across servers and developer laptops.

Expect roughly Rs 8,000–15,000/month (~USD 60–110) for a VPS running a Vault HA cluster—the software itself is self-hosted; you pay for the server infrastructure, not per-secret AWS-style fees.

Choose Vault when apps run on Ubuntu VPS, multi-cloud, or on-prem—not only inside AWS. AWS Secrets Manager fits AWS-native workloads; Vault fits Laravel on VPS setups common on Nepal client projects.

The same database password, payment gateway key, or API token gets copied onto multiple servers, cron hosts, and contractor laptops. Rotation means editing several files manually and hoping nothing breaks overnight. Vault replaces that sprawl with one audited source of truth where access is revocable per identity instead of rotating every credential by hand.

The KV engine stores static secrets like SMTP passwords. The database engine creates temporary MySQL or PostgreSQL users with a TTL you define. The PKI engine issues TLS certificates on demand. Transit encrypts application data without exposing the master encryption key to your PHP code—useful for PII on client portals.

Each engine lives at a mount path like secret/ or database/. Policies are HCL rules granting read, write, or list on specific paths. Auth methods—AppRole, JWT, userpass, or cloud IAM—prove client identity. Vault returns short-lived tokens after successful auth. The seal is the encryption barrier opened by unseal keys or auto-unseal via cloud KMS.

Use a dedicated server or three-node cluster on Ubuntu 24.04—never co-locate Vault on your public web app VM. Install from the official HashiCorp repository and pin the version deliberately. Create /etc/vault.d/vault.hcl with TLS enabled from day one, Raft storage, and the listener on port 8200. Initialize once, store unseal keys and the root token offline, enable audit logging immediately, then revoke or rotate the root token after creating an admin policy.

KV version 2 adds check-and-set, soft delete, and metadata—use it unless you have a legacy reason not to. Enable it at a path your team owns, for example secret/, then write secrets with vault kv put and read them back with vault kv get. Versioning and metadata make rotation and rollback far safer than flat key-value storage.

Never grant broad secret/ read access. Scope each policy to one environment and one service—for example read-only on secret/data/laravel/production and database/creds/laravel-readonly. Apply the policy, enable AppRole auth, bind the role with a one-hour token TTL and four-hour max TTL, and store role ID and secret ID on the server filesystem, not in Git.

Vault connects to MySQL 9.7 or PostgreSQL 18, creates a temporary user with the grants you define in creation statements, and deletes that user when the TTL expires—typically one hour default, twenty-four hour max. Your Laravel app calls vault read database/creds/laravel-readonly at boot or caches credentials until five minutes before expiry. This replaces static DB passwords sitting in .env files across multiple hosts.

Vault Agent is the recommended approach. It runs beside your app, authenticates via AppRole, renders secrets from a template into a generated .env.vault file, and renews tokens automatically. On Deployer 7 releases, write to the shared directory that persists across symlink swaps so each new release inherits secrets without manual copy steps. Reload PHP-FPM after the template renders.

Yes, for queue workers or one-off Artisan commands. Authenticate once in a service provider via AppRole login, fetch secrets, and bind values into the config cache. Cache the token until renewal is needed. Do not call Vault on every HTTP request—that adds latency and generates unnecessary audit log noise.

Configure Vault JWT/OIDC trust so the pipeline receives an id_token. The job exchanges that JWT for a short-lived Vault token via auth/jwt/login, reads deployment secrets like SSH keys, and never stores them as long-lived CI variables. Pair this with scoped roles such as gitlab-deploy rather than using the root token in pipelines—a common and dangerous mistake.

Ansible Vault encrypts inventory variables at rest in your repo—it does not serve secrets to a running PHP-FPM process at runtime. Use Ansible Vault narrowly for bootstrap secrets that install Vault itself. Vault owns everything your application needs while it is running: payment keys, database passwords, SMS tokens, and deployment credentials fetched on demand.

Running a single unsealed node with no Raft storage backup. Using the root token in CI instead of scoped roles. Granting policies with blanket secret/ read on all paths. Storing unseal keys in the same password manager as application passwords. Skipping TLS because traffic is internal only. Skipping audit logging during a pilot and never enabling it. A three-node Raft cluster behind a TLS load balancer with auto-unseal and tested backups is the production baseline.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: