How do Laravel's env:encrypt and env:decrypt commands let a team commit its production environment file, and where must the key live?
answer
- writes .env.encrypted beside .env
- AES-256-CBC by default
- key printed once, base64: prefix
- LARAVEL_ENV_ENCRYPTION_KEY at deploy
- --readable keeps names visible
basics
~10 senv:encrypt writes an encrypted copy such as .env.production.encrypted that can be committed; env:decrypt recreates the plain file during deployment. The key must stay outside the repository, typically in LARAVEL_ENV_ENCRYPTION_KEY on the deploy host.
solid answer
~40 s`php artisan env:encrypt --env=production` reads `.env.production` and writes `.env.production.encrypted` using `AES-256-CBC` by default. Without `--key` it generates a key and prints it once (with a `base64:` prefix) for you to store in a password manager or the deploy platform. The encrypted file is committed; the plain one stays git-ignored. At deploy, `php artisan env:decrypt --env=production` takes the key from `--key` or the `LARAVEL_ENV_ENCRYPTION_KEY` variable and writes `.env.production`, refusing to overwrite an existing file unless you pass `--force`. Laravel never decrypts at boot, so decryption is an explicit deploy step. `--readable` encrypts each value separately and keeps the variable names visible, which makes pull-request diffs reviewable; in Laravel 13 re-running it with the same key keeps unchanged values' ciphertext.
code
bash · 6 lines# on a trusted machine
php artisan env:encrypt --env=production --readable
git add .env.production.encrypted
# in the deploy script, with LARAVEL_ENV_ENCRYPTION_KEY set by the platform
php artisan env:decrypt --env=production --forcego deeper
Know that env:encrypt produces a .env.encrypted file safe to commit and that env:decrypt needs a key to turn it back into a normal .env file.
Explain the --env, --key, --force and --readable options, the default AES-256-CBC cipher, and that the key comes from LARAVEL_ENV_ENCRYPTION_KEY when not passed.
Place decryption in the deploy pipeline before configuration is cached, keep the key only in the platform's secret store, and plan for rotation with --force and a new key.
Weigh encrypted env files against a dedicated secret store for the whole organisation: who can decrypt, how rotation and audit work, and how many apps share one key.
## The problem the commands solve A gym-membership app needs the same production values on every deploy: database credentials, the payment provider key, the webhook secret. Keeping them only in a server's `.env` means nobody can review changes, and a rebuilt server needs someone to retype them. Laravel's answer is to **commit an encrypted copy** of the environment file and decrypt it during deployment, so the repository holds the values and only the deploy target holds the key. ## Encrypting: `env:encrypt` ```bash php artisan env:encrypt --env=production ``` - **Input and output.** Without `--env` it reads `.env` and writes `.env.encrypted`; with `--env=production` it reads `.env.production` and writes `.env.production.encrypted`. - **Cipher.** `AES-256-CBC` unless `--cipher` names another cipher Laravel's encrypter supports. The key length must match the cipher (32 bytes for AES-256). - **Key.** Pass one with `--key`, or let the command generate one. Interactively it asks whether to generate or type a key. A generated key is printed once, in `base64:` form, in the command output. Store it immediately; it cannot be recovered from the encrypted file. - **Overwrite rules.** If the encrypted file already exists the command fails unless you pass `--force` (or use the readable update described below). - **`--prune`** deletes the plain file after encrypting. ## Decrypting: `env:decrypt` ```bash php artisan env:decrypt --env=production --force ``` 1. The key comes from `--key`, or else from the `LARAVEL_ENV_ENCRYPTION_KEY` environment variable, or else an interactive prompt; with none of these the command fails with "A decryption key is required." 2. It reads `.env.production.encrypted` and writes `.env.production` (or a custom location with `--path` and `--filename`). 3. If the output file already exists it fails unless `--force` is given. **Laravel does not decrypt automatically at boot.** The framework only reads plain files, so the deploy script must run `env:decrypt` before the app serves requests (and before any step that builds a config cache). ## The readable format `php artisan env:encrypt --readable` encrypts each value separately and keeps names in plain text: ```ini APP_ENV=eyJpdiI6... PAYMENT_API_KEY=eyJpdiI6... ``` - A reviewer can see that a pull request **added** `PAYMENT_WEBHOOK_SECRET` without seeing its value. - `env:decrypt` detects the format automatically. - Comments and blank lines from the source file are dropped. - Re-running it on an existing readable file requires the same key; since Laravel 13.32 unchanged values keep their old ciphertext, so the diff shows only real changes. `--force` re-encrypts everything, for example to change the key. ## Where the key lives The whole scheme holds only if the key never sits next to the encrypted file: | Place | Suitable for the key? | |---|---| | Deploy platform's secret settings, exported as `LARAVEL_ENV_ENCRYPTION_KEY` | yes | | A password manager used by the people who rotate it | yes | | The same Git repository, any branch | no | | The Docker image or build artefact | no | | `.env.example` | no | Rotating the key means re-encrypting with `--force` and a new key, then updating the deploy secret; anyone who had the old key and a copy of the old file can still read the old values, so rotate the secrets themselves if the key leaks. ## A deploy sequence A typical pipeline for the gym app looks like this: 1. The platform exports `LARAVEL_ENV_ENCRYPTION_KEY` as a secret variable for the production job. 2. The job checks out the commit, which contains `.env.production.encrypted`. 3. It runs `php artisan env:decrypt --env=production --force`, producing `.env.production`. 4. The server exports `APP_ENV=production`, so at boot Laravel selects `.env.production`. 5. The remaining deploy steps (dependencies, migrations, caches) run as usual. If step 3 fails, the deploy should stop: continuing would start the app on stale or missing values. Keeping the plain file out of Git is still required, so `.gitignore` should cover `.env.production` as the skeleton's does. ## What interviewers want to hear - Commit `*.encrypted`, never the plain file. - The decryption key reaches the server through the platform, not the repo. - Decryption is an explicit deploy step, not something Laravel does at boot. - `--readable` makes changes reviewable.
- The deploy log shows env:decrypt failing with 'Environment file already exists.' What happened, and what is the fix?The target `.env.production` was left from a previous deploy, and `env:decrypt` refuses to overwrite an existing file. Add `--force` so each deploy writes the freshly decrypted file, or remove the old file first. Without the fix the server keeps running on stale values, which is exactly what committing the encrypted file was meant to prevent.
- Why would a team choose --readable over the default single-blob encryption?The default encrypts the whole file into one value, so every change produces an unreadable diff. `--readable` keeps each variable name in plain text and encrypts only values, so a reviewer sees which variables were added, removed or renamed. The trade-off is that the names themselves are visible, and comments and blank lines are not kept.
saying these in an interview costs you the question
- Laravel decrypts .env.encrypted automatically when the app boots
- Commit the key in the repository so CI can decrypt
- env:decrypt silently overwrites the existing .env file
- The --readable format stores values in plain text
- Losing the key is fine because env:encrypt can recover it