Laravel secrets without a .env file in production

Keep Laravel's production secrets in Scaleway Secret Manager instead of a .env file on a server, with revisions, env:pull and env:push.

In this post
  1. Does Laravel need a .env file in production?
  2. Why a .env file is a problem
  3. Where Brewless keeps them
  4. Pull, edit, push
  5. Plain values in brewless.yml
  6. When a change takes effect
  7. APP_KEY
  8. What stays outside Secret Manager
  9. Summary

Most Laravel apps start production the same way: copy .env.example to the server, fill in the database password, the mail key and the payment key, and hope nobody copies the file anywhere else. They usually do. A .env file ends up in a server backup, in a Docker image, in a password manager note, and in a chat message to a colleague who "just needs the Mollie key for a second".

This post shows how to run Laravel in production without a .env file: the secrets live in Scaleway Secret Manager in your own account, you change them with two commands, and every change has a revision.

Does Laravel need a .env file in production?

No. Laravel reads its configuration from environment variables, and the .env file is just a convenient way to set them on a development machine. When there is no .env file, Laravel uses the environment the process was started with, so a container that gets its variables from the platform needs no file at all.

That changes where the secrets live. Instead of a file on a disk, they sit in a secret store, and the platform hands them to the container when it starts.

Why a .env file is a problem

.env file on a server Secret store
Where the values are On the disk, readable by anything on it In the store, given to the process at start
History None, unless you keep copies (which is the problem) A revision per change
Who changed what Unknown Written down
Two people editing The last save wins The second save is refused
Copies In backups, images, laptops Only where you pull them

On serverless containers there is a second reason: there is no server to put the file on. The disk does not outlive the container, and a value baked into the image would be in every copy of it.

Where Brewless keeps them

The variables of an environment are kept in Scaleway Secret Manager, in the Scaleway project its application runs in. That is your project, billed to you. Brewless keeps their names and their revisions, never a value. When you ask to see or change the values, Brewless reads or writes them in your Secret Manager and passes them on; they are not stored and not logged, and the fact that someone looked is written to the audit trail.

On each deploy, the release gets one revision of the variables, and the containers start with them as their environment. The default docker/entrypoint.sh runs php artisan config:cache when the container starts, not during the build, because that is the first moment the variables exist.

When Brewless sets an environment up, it writes the first variables itself: the database, the queue, the bucket, and a new application key.

Pull, edit, push

Fetch the variables of an environment into a file:

brewless env:pull production

This writes .env.production, with a first line that notes the revision you pulled. Change what you need, then push it back:

brewless env:push production

env:push replaces all variables of the environment with the contents of the file. Two safeguards come with that:

  • If someone else saved after your pull, the push is refused before it overwrites their revision. Pull again and make your change on top.
  • Variables the push would remove are named, and the client asks before it removes them.

--force skips the question and pushes over a revision you did not pull. Leave it for when you mean exactly that.

.env.production
# revision noted here by env:pull
APP_ENV=production
APP_KEY=base64:your-application-key
DB_PASSWORD=your-database-password
MOLLIE_KEY=your-mollie-key

Warning

.env.production holds real secrets. Check that your .gitignore covers it, and delete the file once the push is done.

Both commands go through Brewless, so your machine needs no Scaleway key. To keep the values away from Brewless altogether, add --direct: the client then reads or writes Secret Manager straight from your machine, with a Scaleway key from SCW_SECRET_KEY or the active profile of Scaleway's own client.

brewless env:push production --direct

The console offers the same under Variables on the page of an environment: the names, a form to set or remove a single variable, and Show values to edit the whole file as a new revision. See Environment variables for every option.

Plain values in brewless.yml

Not every variable is a secret. A locale or a log level can live in your repository, next to the code that reads it:

brewless.yml
environments:
  production:
    variables:
      APP_LOCALE: nl
      LOG_LEVEL: warning

A deploy writes these into a new revision before the build, next to the variables that are there. A variable the file gives is marked Set in brewless.yml in the console, and changing it anywhere else is refused, because the next deploy would put the file's value back.

Brewless refuses a name under variables that looks like a secret: one that contains KEY, SECRET, PASSWORD, TOKEN, CREDENTIALS, PRIVATE, DSN, SALT or AUTH as a word. The deploy stops with the line it is about, before anything is built. See brewless.yml.

When a change takes effect

Variables reach the containers with the next deploy, not before:

brewless env:push production
brewless deploy production

Until you deploy, the console shows that the newest revision is not live yet. That can feel slow when you are fixing a typo in a mail password. It is also what makes a variable change safe: it goes live as part of a release, the release records which revision it ran with, and a rollback takes that revision back along with the code.

Some variables have to exist before a deploy makes sense, such as DB_USERNAME and DB_PASSWORD when Brewless could not make the database user itself. Until the latest revision names each of them, a deploy is refused before the upload, with the missing names. Brewless checks the names only, never the values.

APP_KEY

APP_KEY is the one variable you should think about before you touch it. Laravel encrypts cookies, sessions and anything you store with Crypt or the encrypted cast with it. Change the key, and Laravel can no longer read any of that: everyone is signed out, and encrypted columns become unreadable.

Three cases:

  • A new app. Brewless writes a new application key when it sets the environment up. Leave it as it is.
  • Moving an existing app. Brewless's new key would sign everyone out and lock your encrypted data. Pull the variables, replace APP_KEY with the key the app runs on today, and push before the first deploy. The Vapor guide describes this for an app coming from Laravel Vapor.
  • Rotating the key. Put the new key in APP_KEY and the old one in APP_PREVIOUS_KEYS. Laravel encrypts with the new key and still decrypts with the old ones.
.env.production
APP_KEY=base64:your-new-application-key
APP_PREVIOUS_KEYS=base64:your-old-application-key

Tip

Generate a key on your own machine with php artisan key:generate --show. It prints the key without writing it to a file.

What stays outside Secret Manager

A few secrets are not variables of your app, and Brewless keeps them apart. The read key of a bucket for backups is kept in Secret Manager as a secret of its own and never goes into the variables, so the running app cannot read its own backups. A pipeline token for deploys lives in your CI's secret store, as described in deploying Laravel from CI.

Summary

  1. Production needs no .env file: Laravel reads the container's environment.
  2. Keep the values in Scaleway Secret Manager in your own project; Brewless keeps names and revisions only.
  3. brewless env:pull, edit, brewless env:push, then deploy. Delete the local file.
  4. Plain values may go in brewless.yml; names that look like secrets are refused there.
  5. Carry APP_KEY over when you move, and rotate it with APP_PREVIOUS_KEYS.

Frequently asked questions

Does Laravel need a .env file in production?

No. Laravel reads its configuration from environment variables, and the .env file is only one way to set them. When there is no .env file, Laravel uses the variables the process was started with.

Where does Brewless keep my Laravel environment variables?

In Scaleway Secret Manager, in the Scaleway project your application runs in, which you own. Brewless stores the names and revisions of your variables, never their values, and logs that someone looked at them.

Why did my changed variable not take effect?

Variables reach the containers with the next deploy. Push the change with brewless env:push, then run brewless deploy. Until then the console shows that the newest revision is not live yet.

Can I put variables in brewless.yml?

Only plain values, such as APP_LOCALE or LOG_LEVEL, under variables. A name that looks like a secret, with KEY, SECRET, PASSWORD or TOKEN in it for example, is refused there. Secrets go to Secret Manager with brewless env:push.

What happens if I change APP_KEY?

Laravel can no longer decrypt data, cookies and sessions that were encrypted with the old key. Visitors are signed out and encrypted columns become unreadable. Put the old key in APP_PREVIOUS_KEYS so Laravel can still read what it encrypted before.

All posts Read this post as markdown

Try it on your own app

Brewless deploys your Laravel app to Scaleway and bunny.net, in your own accounts. We open access to Laravel teams in small batches.