skip to content

In Laravel, what does the global `--env` option on an Artisan command actually change, and why can `php artisan migrate --env=staging` still hit your local database?

level: seniorimportance: should knowfreq 25%

answer

  1. environment name comes from argv
  2. .env.staging only if the file exists
  3. missing file: normal lookup, usually .env
  4. cached config: no .env read at all
  5. app()->environment() vs config('app.env')

basics

~20 s

--env sets the environment name the command runs under and, when config is not cached, loads .env.<name> instead of .env if that file exists. Without such a file, or with cached config, the values still come from .env or the cache.

solid answer

~50 s

`--env` is a global option Laravel adds to every Artisan command. During boot the framework reads it straight from the command line and sets the **environment name**, so `app()->environment()` returns `staging`. It also affects which dotenv file is loaded: if config is not cached and `.env.staging` exists, that file is loaded *instead of* `.env`. If the file does not exist, loading falls back to the normal lookup — usually plain `.env` — while the environment still reports `staging`. And if `bootstrap/cache/config.php` exists, no `.env` file is read at all; every value comes from the cached config. So `migrate --env=staging` on a laptop without `.env.staging` migrates whatever `DB_*` points at in `.env`. Environment checks such as the production confirmation follow `--env`; configuration values do not. Confirm the target with `php artisan about --env=staging` or `db:show` before a destructive command.

code

bash · 6 lines
bash
# What will this command really talk to?
php artisan about --env=staging --only=environment,drivers
php artisan db:show --env=staging

# Only then run the destructive command
php artisan migrate --env=staging

go deeper

for a junior

Recall that --env sets the environment a command runs under and can load a matching .env.<name> file.

for a middle

Explain the loading order: .env.<name> replaces .env only if it exists, falls back otherwise, and cached config skips .env loading entirely.

for a senior

Show how this causes wrong-database incidents and how to prevent them: verify the target with about or db:show, keep variant files complete, run commands on the right host.

for a principal

Push environment selection into infrastructure — separate hosts, credentials and cached config per environment — so no single flag can point a command at the wrong data.

## What `--env` is On top of Symfony Console's standard global options, Laravel adds `--env` to every Artisan command, described as *the environment the command should run under*. It appears on every `help` screen and can be passed as `--env=staging` or `--env staging`. During bootstrapping Laravel reads it straight from the process's command-line arguments rather than waiting for normal option parsing, because it must influence loading before any command runs. It only exists for console commands; web requests ignore it. ## The two things it changes **1. The environment name.** When configuration loads, Laravel detects the environment. For a console command with `--env`, the value from the command line wins; otherwise the environment comes from `config('app.env')`. So with `--env=staging`: - `app()->environment()` returns `staging`; - environment checks in code, such as `App::environment('production')`, follow it; - the migration commands' **production confirmation** — which asks "Are you sure you want to run this command?" when the environment is `production` — is triggered by `--env=production` even on a laptop. **2. Which `.env` file is loaded — sometimes.** Before configuration, Laravel loads environment variables from a dotenv file. If the command has `--env` and a file named `.env.staging` exists in the environment directory, that file is loaded **instead of** `.env` — it is a replacement, not an overlay. If `.env.staging` does not exist, loading falls back to the usual lookup: `.env.<APP_ENV>` when the process environment names one, otherwise plain `.env`. ## Where it surprises people | Situation | Environment name | Where `DB_*` and other values come from | |---|---|---| | `.env.staging` exists, config not cached | `staging` | `.env.staging` | | `.env.staging` missing, config not cached | `staging` | usually `.env` | | `bootstrap/cache/config.php` exists | `staging` | the cached config; no `.env` file is read | The second and third rows are the trap. `php artisan migrate --env=staging` on a developer machine without `.env.staging` prints nothing unusual, reports the staging environment, and migrates the database in `.env` — your local one, or worse, whatever the last person pointed it at. On a server where configuration is cached, `--env` changes only the *name*: every configuration value still comes from the cache built at deploy time. It also produces a split you can observe: `app()->environment()` says `staging` while `config('app.env')` still holds the value that was configured. ## How to use it safely 1. Treat `--env` as a way to select a **local variant**, not a way to reach another server. Staging commands belong on staging. 2. Keep variant files explicit: if a workflow relies on `--env=testing`, make sure `.env.testing` exists in every checkout that runs it. 3. Before anything destructive, check what the command will actually talk to: - `php artisan about --env=staging --only=environment,drivers` - `php artisan db:show --env=staging` 4. Remember that cached configuration wins: on a server with cached config, changing environment variables or passing `--env` does not change configuration until the cache is rebuilt. 5. Use `--force` deliberately: on migration commands it skips the production confirmation, including one triggered by `--env=production`. ## A worked example of the incident A developer keeps a `.env.testing` for the test suite and has been told that staging "uses `--env=staging`". They run `php artisan migrate:fresh --env=staging --force` to reset what they believe is a disposable staging schema. There is no `.env.staging` on their machine, so the loader falls back to `.env`, which that week points at a shared development database used by the whole team. The environment name reports `staging`, so no production confirmation appears — and `--force` would have skipped one anyway — and `migrate:fresh` drops every table in the shared database. Nothing in the output looked unusual. Two habits would have stopped it: running `php artisan db:show --env=staging` first, which prints the real connection and host, and never using `--force` with `--env` from a laptop. ## Why interviewers ask The question separates people who have memorised "`--env` selects the environment" from people who know the loading order. It maps directly to real incidents: a migration run against the wrong database, a queue worker started with `--env=production` on a laptop that happily used local mail settings, or a server whose cached configuration ignored every change someone made while "testing with `--env`". The strong answer names the file-exists condition, the cached-config bypass, and a concrete habit for checking the target first.

  • With `.env.staging` present, are values that exist only in `.env` still available when you run a command with `--env=staging`?
    No. When `.env.staging` exists it is loaded instead of `.env`, not layered on top of it, so a key defined only in `.env` is missing unless the process environment provides it. Variant files must therefore be complete, which is why they are usually copied from `.env.example` rather than written as small overrides.
  • On a server with cached configuration, you run `php artisan tinker --env=local`. Which database does Tinker use?
    The one in the cached configuration. With `bootstrap/cache/config.php` present, Laravel skips loading any `.env` file, so `--env=local` changes only the environment name that `app()->environment()` reports. The connection settings were fixed when the cache was built, so Tinker talks to the server's configured database.

saying these in an interview costs you the question

  • --env=staging merges .env.staging on top of .env.
  • If .env.staging is missing, the command fails with an error.
  • --env overrides cached configuration values for that one command.
  • --env also works for web requests when passed as a query parameter.
  • Changing the environment name with --env never affects which confirmations a command asks for.