A .env file committed to git, “temporarily”. Database password, API keys, mailer credentials, all in one file. Symfony 4 moved configuration to environment variables, and this is the second project this month where I see the same thing.

.env is a developer convenience. It exists so local setup does not require exporting fifteen variables by hand before running the app. That is the whole job of this file. .env.dist goes to git with placeholder values, .env stays in .gitignore with your local ones. This part is not negotiable.

Production is a different world. There the variables come from the environment for real: from the systemd unit, from the container definition, from whatever orchestration you have. Not from a file that PHP parses on every request. The Dotenv component says this in its docs, but nobody reads that far. A file with production secrets in the project root, readable by the web server user, one misconfigured vhost away from being served as text.

There is also a build-time versus runtime distinction that bites. Container compilation and cache warmup happen once, at deploy. %env(DATABASE_URL)% is resolved at runtime, later, and that is exactly why it exists. Warm the cache on a build server and runtime resolution is what lets the same artifact run in staging and in production. Bake the value into the compiled container and you lose that.

Now the committed password from the first line. Removing the commit is not enough, git history remembers. A secret that has ever been in git is a former secret. Rotate it today, then fix the gitignore. In that order.

The second project was mine. The file is gone, the password is rotated, and the commit is still there if you know where to look.