Managing production environment variables for Laravel deployments
"Secret secrets are no fun, secret secrets hurt someone" unless managed right in your Laravel deployment! For Flare, we explored alternative solutions to the classic .env
files deployed with every Laravel application. We looked at deploying the .env
file using Ansible, using config servers, the env:encrypt
command, and eventually git-secret, to keep our production environment variables safe and sound.
Environment variables and .env
files have been widely used to manage Laravel apps' application secrets. However, these approaches come with their own set of challenges. In this blog post, we will explore the limitations of traditional methods and discuss alternative solutions for managing environment variables securely.
The limitations of .env and why we need something better
While .env
files and real environment variables are commonly used, they present some challenges in terms of security and manageability:
First, the .env
file is full of sensitive information. It's something you want to avoid committing to a GitHub repo (not even a private one). In an ideal scenario, it's not stored as a plain text file but kept encrypted and only decrypted in memory where needed.
Additionally, managing access between different team members can also be challenging. Once you share access to a server or the location of the .env
file with a new colleague, there's no way back. In an ideal scenario, we'd have a way to view and manage which team members can access each secret.
Another requirement to consider is versioning. A .env
file only kept on the server doesn't keep a history, making it impossible to roll back mistakes.
Finally, deploying changes to the .env
file on a single server is easy. It's manual labour, but it's a 5-minute job. However, the task becomes more challenging when more servers are involved, or worse, Kubernetes.
Alternative Solutions
To overcome these limitations, let's explore a few alternative solutions for securely managing environment variables.
Solution 1: Using Ansible for syncing .env
files
Ansible is an open-source project that provides a simple way to automate various configuration and infrastructure deployments. For example, it's trivial to sync a configuration file to multiple servers using Ansible. And that's precisely what we used to sync our .env
file to all Flare servers simultaneously.
The downside is that this still involves giving people access to the Ansible server, manually updating your application's plain text .env
file, and running an Ansible playbook to deploy the updated .env
to all servers. While it worked well enough for years, it would be nice to manage and deploy .env
secrets closer to home. For example, in the repo itself...
Solution 2: Laravel's env:encrypt
Starting from Laravel 9, the env:encrypt
command can be used to encrypt the .env
file using in a way that the encrypted file can be committed to the GitHub repo. The .env.encrypted
can be deployed and decrypted to all production servers. Because the encrypted .env
file is stored in the GitHub repo, it is also versioned. This approach checks a lot of boxes, but it still has one single drawback: access is managed through a single encryption key that needs to be shared and protected. This means there is no way to revoke access from a single user.
Solution 3: Secret management products
The various secret management products are arguably the best alternative for .env
. For the big three, we have AWS Parameter Store, Google Secrets Manager, and Azure Key Vault (why do all of these products have different names for the same concept?). Another honorable mention is HashiCorp Vault. These solutions are made specifically for this problem and provide a ton of extra advantages:
-
Automatic secret rotation: config servers can rotate secrets automatically, reducing the risk of compromised credentials.
-
Granular access control allows you to give specific users access to individual secrets.
-
Integration possibilities: Config servers typically don't write to a
.env
file. Instead, they'll decrypt the secrets to memory at runtime, avoiding the various risks of a plain text.env
file -
Built-in audition solutions
-
Real-time updates (depending on the application)
While this seems like the ideal solution, it is another service to manage or pay for, and for now, it feels a bit too heavy for our use case.
Solution 4: git-secret - What we ended up using at Flare
git-secret is built to solve the exact problem of managing secrets close to the git repository. Working very similar to Laravel's own env:encrypt
command, its most significant advantage is that access is managed through a GPG keyring instead of a single shared encryption key. This means we can give and revoke access to each team member through their public key.
Additionally, it checks all the other boxes:
-
Secrets are stored directly in the repository, making managing and tracking changes easy.
-
No additional infrastructure or services are required.
-
Built right into existing tooling: We already use git everywhere during development and deployment.
-
Deploys stay super simple: one extra command is all it takes to deploy the encrypted
.env
file.
Let's switch from theory to practice and look at how we set up git-secret & add the public key for the first user.
Setting up git-secret
-
Install git-secret on your local machine and servers. Because git-secret is an extension to the Git CLI, it must be installed separately. Installation instructions can be found here.
-
Create a new GPG key pair using the
gpg --gen-key
command. -
Export the public key for your newly created GPG key pair:
gpg --armor --export [email protected] > public-key.gpg
-
Initialize git-secret by running the
git secret init
command in your app's repository. The relevant sensitive files will automatically be added to your.gitignore
, but checking either way is a good idea. -
Use
git secret add .env.production
to add the.env.production
to this repo's list of encrypted files. Again, the relevant sensitive files will automatically be added to your.gitignore
. -
Run
git secret hide -d' to encrypt the
.envfile. Because the
-d' flag was used, the original file will also be deleted. -
Share the secret with the user's newly created public key using
git secret tell <[email protected]>
-
You can now test decrypting the encrypted secret using
git secret reveal
. If all goes well, the.env.production
file should show back up.
After you're ready to deploy your application to production, git secret hide -d
, commit, and deploy to production. On the production server (or as the last step in the deploy script), we can now decrypt the .env
file using the same git secret reveal
command.
Like any good tool, many extra features, options, and configurations exist. You can read about it in the git-secret docs.
Some notes on running git-secret in production
The encrypted secrets are binary files. This makes it impossible (or annoyingly hard) to merge them in PRs or other branching actions. That's why it's essential to only commit changes to the encrypted .env
files on a single branch and to push them to the remote repo immediately. This way, we can avoid all merge conflicts with these binary files.
Another good tip is to set up a separate Git repository to manage the public keys of your team. Using gpg --import <key>
, you can quickly import these keys into your GPG keychain when necessary.
Conclusion
Managing environment variables securely is crucial for Laravel apps. While traditional methods like .env files have limitations, alternative solutions provide better security and manageability. This post discussed solutions like the various secret management products, Laravel's own env:encrypt
command, Ansible, and git-secret.
For Flare and our team, git-secret emerged as a comprehensive solution, offering user-specific access control and seamless integration in our existing development and deployment flow.