Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Vault Bundle Laravel Package

itk-dev/vault-bundle

Symfony bundle for integrating HashiCorp Vault with your PHP application. Configure Vault connections and authentication, read and manage secrets, and inject Vault-backed values into Symfony services and configuration. Helps keep sensitive credentials out of code and environment files.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle that bootstraps HashiCorp Vault secrets into your application's environment before the container is compiled, so %env(DATABASE_PASSWORD)% resolves to a value that never lived in a committed .env file. It targets the "Vault replaces .env.local in deployed environments" problem — not the same thing as Symfony's built-in symfony/vault (Sodium-encrypted secrets committed to the repo).

Minimum path to first secret:

composer require itk-dev/vault-bundle:^1.0
  1. Register the bundle in config/bundles.php (it will not be auto-registered on older Symfony skeleton versions).
  2. Set connection env vars — typically VAULT_ADDR plus either VAULT_TOKEN (local dev) or an AppRole pair (VAULT_ROLE_ID / VAULT_SECRET_ID) for deployed environments. Check the README for the exact names the bundle reads; ITK Dev's bundles generally read unprefixed VAULT_* variables rather than nesting under a vault: config key.
  3. Point it at a path, e.g. secret/data/myapp/dev, and give each key in that Vault secret the name of an env var you already reference (DATABASE_URL, MAILER_DSN, APP_SECRET).
  4. Read the bootstrapping code first, not the README — vendor/itk-dev/vault-bundle/src/ is small, and the important question ("at what point in the kernel lifecycle does this run?") is answered in about 60 lines of PHP. Also run bin/console list vault (or debug:container --env-vars) to see what surface actually got registered in your version. Then run bin/console config:dump-reference vault to see real config keys rather than guessing.

As of 1.0.0 the bundle declares Symfony 8 support alongside the earlier supported versions, so it now spans the Symfony 6/7/8 range — check the composer.json constraint to confirm the floor matches your app, and treat 1.0 as the first release where the config surface is meant to be stable.

The canonical first use case: move APP_SECRET and the database password out of .env.local, leave everything else alone, and confirm the app boots with an empty local .env while VAULT_ADDR/token are present.

Implementation Patterns

Two auth strategies, one config file. Use token auth against a local dev Vault (docker run hashicorp/vault, vault server -dev) and AppRole everywhere else. Keep the switch in .env only:

# .env (committed)
VAULT_ADDR=https://vault.internal:8200
VAULT_SECRET_PATH=secret/data/myapp/%kernel.environment%
# .env.local — token for dev
VAULT_TOKEN=root

Path-per-application, environment as a suffix. The secret/data/<app>/<env> convention makes Vault policies trivial (path "secret/data/myapp/prod" { capabilities = ["read"] }) and stops a staging token from reading production. Resist a single flat path with APP_ENV-prefixed keys; the policy language can't express that.

Flatten before injecting. Vault KV values are JSON, but Symfony env vars are strings. Keep every leaf value a plain string on the Vault side; if you must store structured data, write a small normaliser that flattens nested maps to APP_DB_HOST, APP_DB_PORT etc. before they hit $_ENV. The bundle will not do this for you.

Local dev fallback. The single most important pattern: make the Vault fetch optional. Either guard on a VAULT_ENABLED=0 flag or wrap the bootstrap in a try/catch that logs and falls through to .env.local. Without this, every developer without VPN access, and every CI job that only runs composer install, gets a hard boot failure.

CI is where this pays off. In GitHub Actions, either (a) run a throwaway Vault service container seeded from repo secrets and let the bundle fetch at cache:warmup time, or (b) vault kv get -format=json ... | jq -r and export vars in a step before bin/console cache:clear. Option (b) is more legible in logs and doesn't require the bundle to work headlessly.

Kubernetes: consider skipping the bundle. If you're on k8s, the Vault Agent Injector sidecar gives you auto-renewal, file-based secrets, and no PHP-level Vault dependency for roughly the same YAML effort. Use this bundle for VM/bare-metal/Platform.sh-style deploys where there's no sidecar runtime.

Don't confuse it with Symfony Secrets. If your threat model is "secrets in git, encrypted at rest," symfony/vault + a committed *.enc file is simpler, has no runtime dependency, and is maintained by the Symfony core team. This bundle earns its place only when you already run Vault as organisational infrastructure.

Gotchas and Tips

  • Hard dependency on Vault availability at boot. If the fetch happens during kernel boot or container compilation, Vault being down = the app is down, including bin/console and cache:clear. Always have a degraded mode.
  • Compiled secrets live in var/cache. Because env vars are resolved at container build time, secret values end up dumped into the compiled container. Consequences: (1) cache:clear is mandatory after rotation, not optional; (2) never bake var/cache into a shared image layer; (3) treat the cache directory as secret material in file permissions.
  • KV v1 vs v2 double-data. With the v2 engine the response is {"data": {"data": {...}, "metadata": {...}}}. Every hand-rolled cURL example you'll copy gets this wrong. If your secret path is /secret/myapp in v1 it becomes /secret/data/myapp in v2.
  • Escape special characters. Symfony env values containing !, %, or $ need care (! is a PHP ini-style escape in some contexts); passwords generated by vault write -f ... -format=json | jq -r '.data.password' are frequently base64 or contain =. Test the roundtrip, and prefer DATABASE_URL-style single DSNs over decomposing into host/user/pass, which multiplies the escaping surface.
  • Rotation has no callback. Nothing in a bundle-level injection scheme watches for changed secrets. If you need live rotation, either restart the worker or write an event subscriber that re-reads on a timer — but be honest that you're then outside the bundle's design.
  • Debugging without leaking. Use bin/console debug:container --env-vars to confirm a variable is present and which file/mechanism set it, and compare against vault kv get output. Print lengths and hashes, never values. Turn on Vault's audit device (vault audit enable file file_path=/vault/logs/audit.log) so you can see whether the app's token is actually requesting the path you think it is — 403s from a path typo look identical to auth failures in most logs.
  • Least-privilege AppRole hygiene. Scope the policy to the exact path and read only; set token_ttl short (e.g. 1h) and let the bundle authenticate per-boot rather than holding long-lived tokens; consider secret_id_bound_cidrs so a leaked secret_id is useless off-network.
  • Extension points are thin, so decorate. If you need custom retry logic, a Vault Enterprise namespace header, or a caching layer, wrap the bundle's client service in a decorator (decorates: in services.yaml) rather than forking. If the bundle isn't already injecting at the moment you need (e.g. you have config that must resolve even earlier), a small Kernel::boot() shim or a Dotenv-style bootstrapper in public/index.php may be the pragmatic answer.
  • The 1.0.0 bump is a semver promise, not a functional leap. The release is a hotfix rollup plus Symfony 8 support — no new features and nothing documented as breaking versus 0.1.2 — but it does mean the maintainers are now committing to the public surface. Prefer a caret constraint (^1.0) over an exact pin and let patch/minor updates flow with a changelog read; anything that would force a 2.0 is the signal to re-read the diff before upgrading.
  • Symphony 8 support widens the matrix; verify your own combination. Support for Symfony 8 means the bundle is tested against a wider version range, not that every combination of Symfony 6/7/8 and PHP version is equally exercised. If you're pinned to an older Symfony, run your own boot test (bin/console cache:clear with a real Vault) rather than assuming CI covered it.
  • Weigh the bus factor — improved but still thin. Two stars, zero dependents, a single-organisation maintenance profile; 1.0.0 brings a second maintainer (@rimi-itk) and a stable API, which is a real improvement, but it's still a small project. Keep the functionality in mind as something you could inline into your own src/ if it stalls — it's a few hundred lines, and for a piece of infrastructure this close to your secrets, owning it may be the better long-term trade.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle