Deploying Laravel with Elastic Beanstalk - .env
Deploying on a Digital Ocean instance is pretty straightforward. Once you get your instance up, if you are using Forge, you can simply add your .env key:values by going to the server, then the site you want to edit, click the Environment tab, and then click Edit Environment button.
If not on Forge, you ssh into the server, navigate to the root of the site you want to edit, vim .env and add your key:values.
You might think you can just ssh into the Beanstalk server instance that was created and add your .env file there...well, that will work, but only until Beanstalk creates a new server instance. Beanstalk just throws away a server and everything on it when it needs to. So manually adding the .env file this way is not great because it will be gone the next time you deploy your code.
So we need a way to get our env values deployed each time Beanstalk creates a server instance.
This took me a while to figure out, I found some people who were trying to copy a file from S3 in a build script...but that seemed more complicated than I wanted. I kept thinking there has to be a way to set environment variables that is secure in Beanstalk, and there is!
It's kind of similar to Forge, but it isn't as easy to find. Go to your Beanstalk dashboard, click on the app you want to configure, then click on the Configuration link in the left side navigation, and you should see something like this image:
In the Software Configuration panel, click on the Gear icon. You will be taken to a page, scroll down toward the bottom and you will see this:
You can add your .env keys and values here.
How does this work though?
Yeah, I wasn't sure if this would work either, but to understand it you have to know how the .env values work in Laravel.
Laravel uses the phpdotenv package, when the app is created (on a request) it loads the values from the .env into the php global
$_SERVER variables. So now the question is how is do we get Beanstalk to load them?
Well when Beanstalk builds your server instance, it looks in this list of variables you create and loads them into the
$_SERVER globals too.
Looking deeper in the phpdotenv package, you see that the globals are overwritten if a key is matched in the .env file. So this means that we simply don't deploy a .env file on our staging and production environments which are run by Beanstalk.
There is a little trick though
I think Beanstalk can at times build new RDS instances too, although I am not sure if the passwords are retained to new instances.
The good thing is, AWS makes this really easy. It automatically loads the RDS credentials into the
$_SERVER global for you, so you can access them like this:
$_SERVER['RDS_HOSTNAME'] $_SERVER['RDS_DB_NAME'] $_SERVER['RDS_USERNAME'] $_SERVER['RDS_PASSWORD']
Now all you have to do is update your
config/database.php file like this:
// code omitted 'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST') ?: $_SERVER['RDS_HOSTNAME'], 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE') ?: $_SERVER['RDS_DB_NAME'], 'username' => env('DB_USERNAME') ?: $_SERVER['RDS_USERNAME'], 'password' => env('DB_PASSWORD') ?: $_SERVER['RDS_PASSWORD'],
Notice there is no default value being passed to the
env() functions?, this means if they are not set the function will return null. Using the short ternary operator we can then tell it to use our RDS credentials.
Now whenever a new EC2 server instance is created, it will have access to all the correct environment variables...and if Beanstalk ever needs to create a new RDS instance, the EC2 instances will be updated with whatever password is being used at the time.
One last thing to note, all the .env values are treated as strings by the phpdotenv package, the
env() function in Laravel casts a couple values to booleans (for example, it casts string 'false' to false...because php would treat it as true) so everything you enter in the Beanstalk environment interface is nothing more than a string. The
APP_KEY value has base64:somecrazystring but that doesn't really mean anything when you are storing it. Laravel just parses the beginning of the string and looks for base64: which determines if it should base64encode the string or not.
Well, I hope this helps other people looking to do the same thing.