A Laravel 13 release's caches were built by php artisan optimize in a CI job that lacked production environment variables; what breaks in production, and which caches are safe to build there?
answer
- config cache is a value snapshot
- build-time env baked into config.php
- absolute paths are captured too
- routes and events rarely depend on env
- optimize --except=config
basics
~20 sconfig:cache freezes resolved values, so production runs on the CI job's credentials, key and paths and ignores its own environment. Route, event and view caches mostly depend on code; cache config where production values exist.
solid answer
~40 s`config:cache` boots the app, evaluates every `config/*.php` file (including their `env()` calls) and writes the resulting array to `bootstrap/cache/config.php`. Built in CI, that file holds CI's values: empty or default database credentials, a missing or wrong `APP_KEY`, `APP_ENV` other than production, and absolute paths such as `storage_path()` from the CI workspace. Production then loads that snapshot and never reads its own environment, so you see failed connections, decryption errors and wrong behaviour. Route, event and view caches normally depend only on code, so they can be built early with `php artisan optimize --except=config`, unless a route file branches on config or environment. Config caching belongs where the production environment is present: on the server during the deploy, or at container start.
go deeper
Recall that config:cache writes resolved values, so it must run where the production environment variables exist.
Explain which of the four caches depend on the environment and why routes, events and views are mostly code-only.
Design the build and deploy split with optimize --except=config, and diagnose production failures caused by a snapshot built elsewhere.
Decide where environment binding happens in the release pipeline and enforce it with checks rather than tribal knowledge.
## What each cache actually captures The four caches that `php artisan optimize` builds are not equally sensitive to where they are built. The deciding question is: **does the cached output depend on the environment, or only on the code?** | Cache | Built from | Depends on environment? | |---|---|---| | `config:cache` → `bootstrap/cache/config.php` | every `config/*.php` file, evaluated | **yes** — every `env()` call and every path helper is resolved at build time | | `route:cache` → `bootstrap/cache/routes-v7.php` | route files, executed once | usually no; yes if a route file branches on config or environment | | `event:cache` → `bootstrap/cache/events.php` | discovered listeners and `$listen` arrays | normally no; it is class and method metadata | | `view:cache` → `storage/framework/views` | Blade templates compiled to PHP | no for the output; the compiled filenames depend on template paths | ## Why the config cache is the dangerous one `ConfigCacheCommand` boots a fresh application, calls `$app['config']->all()` and writes the array with `var_export`. Everything a config file computed is frozen as a literal: - `env('DB_PASSWORD')` becomes whatever the CI job had, often `null` or a test value; - `env('APP_KEY')` becomes CI's key or nothing, so encrypted cookies and `Crypt` payloads fail to decrypt in production; - `env('APP_ENV', 'production')` may become `testing` or `local`, changing environment checks throughout the app; - `storage_path()`, `base_path()` and similar helpers become absolute paths from the CI workspace, which may not exist on the server. At runtime, `LoadConfiguration` requires that file and `LoadEnvironmentVariables` skips `.env` because configuration is cached. The production server's real environment is never consulted. The failure is often confusing because the code is correct and the environment is correct; only the snapshot is wrong. ## Routes, events and views The other three caches are mostly environment-independent: - **Routes.** The cache is a compiled route collection. It is only environment-dependent if a route file registers routes conditionally (for example, only when debug is on) or computes a domain from config. Those decisions are made once, at build time. - **Events.** The manifest maps event classes to listener methods, derived from code. - **Views.** Compiled Blade is PHP code generated from templates. By default the compiled filename is a hash of the template's absolute path, so if the application lives at a different path at runtime, Blade simply compiles again on demand rather than using the prebuilt files. Setting `view.relative_hash` makes the hash relative to the base path instead. ## A workable split 1. In CI or the image build, run `php artisan optimize --except=config` (the `--except` option accepts the key `config` or the command name `config:cache`). 2. On the server during the deploy, or at container start where the real environment exists, run `php artisan config:cache` (or `php artisan optimize`, which rebuilds everything). 3. Never commit or ship a `bootstrap/cache/config.php` built elsewhere. ```bash # build stage: code-only caches php artisan optimize --except=config # runtime/deploy stage: environment is present php artisan config:cache ``` ## Symptoms that point to a foreign config cache - database connection errors naming a host, database or user that only exists in CI; - `Crypt` calls throwing `DecryptException` with "The MAC is invalid.", and every user appearing logged out because their encrypted cookies are discarded; - `MissingAppKeyException` ("No application encryption key has been specified.") when CI had no key at all; - `app()->environment()` reporting `testing` or `local` on a production server; - errors about log or storage paths under the CI workspace directory. Each of these disappears the moment `php artisan config:cache` runs on the server itself, which is a quick way to confirm the diagnosis. ## Checks worth adding - Fail the build if `bootstrap/cache/config.php` exists in the artifact. - After the deploy, hit an endpoint that exercises the database and encryption, and check a harmless value such as `app.env` from the running app. - Treat any environment change as a reason to rebuild the config cache; a hosting panel edit alone does nothing to a cached snapshot. ## Key points - The config cache is **values**, not code; build it where the values are real. - Route, event and view caches are **code artifacts** and can be built early, with the route-file caveat. - `optimize --except=config` is the clean way to split the two.
- Why are users logged out, and stored encrypted values unreadable, when a Laravel app runs on a config cache built in CI?The cached `app.key` is CI's key or empty, not production's `APP_KEY`, and with config cached the real environment is never read. Cookies encrypted with the production key fail to decrypt and are discarded, so sessions are lost, while `Crypt` calls on stored data throw `DecryptException`. Rebuilding the config cache on the server fixes both.
- When is building the route cache in CI not safe either?When a route file registers routes conditionally on configuration or environment, such as a debug-only route or a domain read from config. `route:cache` executes the route files once, in the build's environment, and freezes those decisions. Either remove the conditions from route files or build the route cache where the production environment is present.
saying these in an interview costs you the question
- config:cache stores env() calls and resolves them per request
- Production environment variables override a cached config file
- The route cache is as environment-sensitive as the config cache
- Committing bootstrap/cache/config.php speeds up every deploy safely
- Compiled Blade views embed database credentials