How to deploy safe ZF 2 applications on Amazon Elastic Beanstalk
One question that often arise is how to deal with multiple environments (production, development…) and how not to compromise your credentials. This short article focus on how to deploy your application on Amazon Elastic Beanstalk, which is a PaaS system that I love.
The first mistake not to do is to commit configuration files that contain credentials (like your database password, or the secret key to your mail provider). This is why Zend Framework 2 introduced the concept of local and global config files (learn more about this here). To be short, those files are saved in your autoload folder, and allow to configure a module. local files should NEVER be pushed to your version control system.
Let’s take the example of configuring Doctrine 2 ORM using Doctrine Module. We are going to create two files: doctrine_module.config.global.php and doctrine_module.config.local.php. The global file will be pushed to Git (and to Elastic Beanstalk as it uses Git deployment), and will contain production settings. On the other hand, the local file will contain development settings and access to your local database. As local files overwrite global files, it works nicely.
Here is the global file, with production settings (this one will be on your computer AND will be pushed to Elastic Beanstalk):
return array( 'doctrine' => array( 'connection' => array( 'orm_default' => array( 'configuration' => 'orm_default', 'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', 'params' => array( 'host' => $_SERVER['RDS_HOSTNAME'], 'port' => $_SERVER['RDS_PORT'], 'user' => $_SERVER['RDS_USERNAME'], 'password' => $_SERVER['RDS_PASSWORD'], 'dbname' => $_SERVER['RDS_DB_NAME'], ) ), ), 'cache' => array( 'class' => 'Doctrine\Common\Cache\ApcCache' ), 'configuration' => array( 'orm_default' => array( 'metadata_cache' => 'apc', 'query_cache' => 'apc', 'result_cache' => 'apc', 'generate_proxies' => false, ) ), ) );
Notice that it has APC cache, does not regenerate proxies… One interesting thing is the $_SERVER[‘RDS_’]* lines. If you create your Elastic Beanstalk environment with a RDS database, Elastic Beanstalk will automatically give you those environment variables for free! This means that this file can be committed _without _explicitly writing the production credentials, which is nice.
Here is the local files (this one is only on your development computer, and is not committed to Git nor Beanstalk if you configured the .gitignore correctly):
return array( 'doctrine' => array( 'connection' => array( 'orm_default' => array( 'params' => array( 'host' => 'localhost', 'port' => '3306', 'user' => 'root', 'password' => '', 'dbname' => 'db_name', ) ), ), 'cache' => array( 'class' => 'Doctrine\Common\Cache\ArrayCache' ), 'configuration' => array( 'orm_default' => array( 'metadata_cache' => 'array', 'query_cache' => 'array', 'result_cache' => 'array', 'generate_proxies' => false ) ), ) );
This works well because Elastic Beanstalk provides RDS_* environment variables for us. But let’s say we want to do the same with Mandrill, an email provider. We don’t want to commit our secret key.
As for before, let’s create a “mandrill.global.php” file:
return array( 'mandrill' => array( 'key' => $_SERVER['MANDRILL_KEY'] ) );
While the mandrill.local.php file contains the secret key hardcoded (once again, remember it’s not committed !):
return array( 'mandrill' => array( 'key' => 'my-mandrill-super-key' ) );
The problem is that Elastic Beanstalk does not know the “MANDRILL_KEY” environment variable. In order to do that, let’s create a folder called “.ebextensions” at the root at your website, and create a “script.config” file. Those files are used to customized the underlying EC2 instance without ever log to the instance. Learn more on that here.
In this script.config file, add the following YAML content:
option_settings: - namespace: aws:elasticbeanstalk:application:environment option_name: MANDRILL_KEY value: mandrill-key-placeholder
This code simply create a new environment variable with a default placeholder (don’t write your real credential here, as this file is pushed to Git !). Deploy the new application to Elastic Beanstalk. Then, log in to your Amazon account and, through the console, choose “Edit/load configuration”, and open the “Container” tab. Scroll down and it added a new option:
Now, you can safely write the real credential !
I personally find this way very convenient. It adds a security layer by not pushing credentials to Git, and it keeps deployment simple.