Which configuration source should own secrets in CakePHP: env() placeholders in app.php or a deployed app_local.php?
0 reputation · 31 Jan 2025, 14:14 UTC
0 reputation · 31 Jan 2025, 14:14 UTC
We run a CakePHP application where everything works locally but behaves differently in production, and the root cause keeps coming back to configuration layering. CakePHP loads config/app.php and then merges config/app_local.php on top, but the skeleton gitignores app_local.php, so any value defined only there silently vanishes on a fresh production deploy. Values can also come from environment variables via env() calls inside app.php (for example DATABASE_URL or Security.salt), which means the effective configuration depends on the process environment rather than committed files.
The unresolved decision for our team is where environment-specific secrets and overrides should canonically live. Committing app.php with env() placeholders centralizes defaults but pushes secret management entirely into the deployment platform, while deploying a per-environment app_local.php keeps overrides explicit but outside version control and harder to audit or rotate. CakePHP supports both and does not mandate one. We also know stale compiled caches under tmp/cache can mask config changes, which complicates comparing the two approaches.
Assume a current CakePHP 4.x/5.x application deployed behind a standard PHP-FPM setup.
Which approach do teams treat as the single source of truth for production secrets, and why? Does mixing both (defaults in app.php, overrides in app_local.php) create precedence pitfalls worth avoiding? What is the safest way to verify which source actually supplied a given value in the running production process?
32775 reputation · 31 Jan 2025, 23:57 UTC
The canonical source of truth for production secrets in CakePHP is the environment variables that env() reads from config/app.php. app_local.php should be reserved for non‑secret, environment‑specific overrides that are kept out of version control.
Configure::load('app') merges config/app.php and config/app_local.php, app_local.php wins. A missing DATABASE_URL in the environment can be silently replaced by a stale value in app_local.php, masking the real problem.app_local.php is git‑ignored, a fresh deployment that does not copy the file will lose those overrides, leading to unpredictable behaviour.app_local.php if they are not disciplined, re‑introducing the very risk you are trying to avoid.config/app.php first.config/app_local.php on top.app_local.php overrides the same key from app.php.Because of this order, env() values in app.php are only used if app_local.php does not provide the same key.
rm -rf tmp/cache/*
php -r "require 'config/bootstrap.php'; echo Configure::read('Security.salt');"
php -r "echo getenv('SECURITY_SALT');"
app_local.php (or a cached config) supplied it.config/app_local.php and repeat the steps. A change in the output confirms that app_local.php was overriding the environment value.config/app.php with env() placeholders but no hard‑coded secrets.config/app_local.php for local, non‑secret tweaks (e.g., database host for a dev VM) and add it to .gitignore.app_local.php or in the cache.If you are using a container platform that mounts environment variables at runtime, confirm that the variables are indeed present in the process environment before the PHP bootstrap. You can do this by adding echo getenv('YOUR_SECRET'); to a temporary bootstrap file and inspecting the output in the container logs.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.