Guide
A Comprehensive Agent's Guide to Laravel CloudConfigure environments
· Ed Grosvenor
Laravel Cloud configures an application's connections for you. When it attaches a database, cache, bucket or managed queue, it writes the credentials and the driver selection into a separate, managed environment file. Your own environment variables sit on top of that file. Set one of the same names and you replace Cloud's value with yours.
Never set what Cloud injects
| Resource | Cloud sets | Do not set |
|---|---|---|
| Database | Connection details and the default connection | DB_*, DB_CONNECTION, DATABASE_URL |
| Cache (Valkey or Redis) | Connection details, CACHE_STORE, and the session driver |
REDIS_*, CACHE_STORE, SESSION_DRIVER |
| Bucket | Credentials and the default disk | AWS_*, FILESYSTEM_DISK |
| Managed queue | Queue credentials and connection | SQS_*, QUEUE_CONNECTION |
Overriding these broke things for us twice. An agent set QUEUE_CONNECTION=sqs and production stopped processing jobs, because Cloud's managed queues use a connection named cloud. A leftover override from an earlier setup stranded about 3,900 jobs on a queue nothing was reading.
The same rule applies to documentation you write. A README or .env.example listing QUEUE_CONNECTION=redis or five DB_* variables as "required in production" teaches the next person to break their deployment. And cloud ship offers to copy your local environment file up to Cloud. Decline.
A few consequences of the separate file:
- Interpolation does not work across it.
MY_DB_HOST=${DB_HOST}cannot resolve on Cloud, becauseDB_HOSTis not in the file being interpolated. Read the resolved connection fromconfig()instead. AWS_URLis not injected. If your code builds public object URLs from it, set it yourself or derive the URL another way.- A package that builds its own connection from its own variables, with a fallback such as
127.0.0.1, will never reach the database on Cloud. Have it reuse the application's resolved default connection.
Defaults that surprise
APP_ENVis not set. Laravel treats an unsetAPP_ENVasproduction, so every environment, staging included, runs as production. Destructive commands such asmigrate:freshrefuse to run there. SetAPP_ENVexplicitly on non-production environments.- The session driver is
cookie. Cloud setsSESSION_DRIVER=cookieunless a cache provides something else. Laravel's own default isdatabase, and an app that relies on that hides the difference until it reaches Cloud. A database session driver on public pages also keeps a hibernating app's database awake. See Hibernation and idle cost. - Values we have read from running environments: the default database connection was
pgsql, the default queue connectioncloud, the default diskprivate(wired to the bucket), and the cache storedatabasewhen no cache was attached. - Bucket disk configuration is replaced, not merged. Cloud's bootstrapper replaces your configured disk with a fixed set of keys. Options you added, such as
stream_reads, disappear, andthrowis forced tofalse. Large reads then buffer whole objects in memory, which matters on a 512 MB instance.
Read configuration through config()
Do not assume whether configuration is cached. Our notes disagree: one production environment reported config, events and routes as not cached, while another application behaved as if optimize had run during deploy. Check with:
cloud command:run <environment-id> --cmd='php artisan about --only=cache' --json -n --fields=exitCode,output
Then write code that works either way. Under cached configuration, env() returns null outside configuration files, and Laravel never loads the environment repository. Code that asks "was this variable explicitly set?" gets the wrong answer. Decide from config() values only, and add a test that boots the application with cached configuration, since test suites usually never do.
If you want caching, add php artisan optimize to the build command.
Changing variables
cloud environment:variables <environment-id> --action=set --key=FEATURE_FLAG --value=on --force -n
cloud environment:variables <environment-id> --action=delete --key=OLD_A,OLD_B --force -n
- Changes apply at the next deploy. Running containers keep the old values. That also means removing a variable does not stop a running process from using it.
- A redeploy ships whatever is on the tracked branch. Before redeploying just to pick up a variable, compare the current deployment's commit with the branch head. You may be deploying merged work nobody meant to release yet.
appendadds without checking for duplicates.setreplaces matching keys. Usesetso a rerun converges.- Verify with a re-read, such as
cloud environment:get <environment-id> --json -n --fields=name,environmentVariables, not the exit code. - The read shows only variables you set. Cloud's injected values never appear there. To see what the running application actually received, ask it with
command:runortinker. - Variable names must match
^[a-zA-Z_][a-zA-Z0-9_]*$, and one API call accepts at most 200 variables.
Prove configuration against the running app
Configuration files and environment listings describe intent. Before declaring an environment correctly configured, check from inside it:
- the effective values, read with
config('database.default'),config('queue.default')and so on; - a database connection, with
DB::connection()->getPdo(); - the default disk, with a
put,getanddeleteround trip; - a queued job actually processed, not just dispatched.
Two configured variables are not proof that anything works. One of our applications had telemetry variables set for weeks while its credential's request count stayed at zero. Nothing had ever arrived.