How our team implements a Multiple Environment Configuration
Setting up Subdomains
When we set up our multiple environment configuration, we usually use a minimum of 3 domains. These are dev, static, and www. Most documentation will tell you to use local, dev and www. But we like to do things differently because we tend to work on the dev domain instead of locally as we usually have a number of us working on the site at once. Here at A Digital we also like to use the static subdomain environment for all of our assets used in the cms, of course you can use another name such as media or cdn for this subdomain.
By allowing the live and dev sites to share assets, when we pull a fresh copy of the live database into the dev site we are not missing any images and all of our content is working correctly. It also means that instead of storing the images on our server in 2 separate locations, we only need them in one place. This isn’t just a preference on our part though, speed checking sites such as pingdom recommend that you store static assets on a cookieless domain which is made possible using this kind of configuration.
Our files for each environment will all sit on the same domain but under their own subdomain folders. To make this clear we have highlighted the root folder for each of the domains and the location of the cms in our multiple environment configuration, here is our file structure for a few different scenarios:
As you can see from the images above, there is no strict structure that you must follow when setting up your environments file tree on the server. It is purely subjective and down to your own preference, if you like to group all of your subdomains into a “subdomains” folder then you can go ahead and do that, or you can have them all at a root level. The only thing you must do is make sure that the server path to these folders can be included into your configuration settings.
We will now show you how we set this up for both Craft and ExpressionEngine here at A Digital. These are the content management systems that we use most frequently and if you are looking for how to do this on wordpress, statamic, drupal, etc then there should be guides available on their official documentation. However, the lessons we’ve learned across Craft and ExpressionEngine can be applied to almost any CMS in a multiple environment configuration.
Implementing it into Craft
Unlike ExpressionEngine, Craft has multiple environment configuration capabilities baked in. It is a core feature that is well documented over at https://craftcms.com/docs/multi-environment-configs and this means that it is potentially quicker to implement for first time users. All you need to do is modify the general.php and db.php files in your /craft/config folder. An array with a key of “*” captures settings to be applied to all instances, and then you just set each instance's variables underneath using the domain as the key for these arrays. Here is an example:
define('URI_SCHEME', ( isset($_SERVER['HTTPS'] ) ) ? "https://" : "http://" );
define('SITE_URL', URI_SCHEME . $_SERVER['SERVER_NAME'] . '/');
define('BASEPATH', realpath(dirname(__FILE__) . '/../') . '/');
return array(
'*' => array(
'cpTrigger' => 'cms',
'omitScriptNameInUrls' => true,
'cooldownDuration' => 0,
'environmentVariables' => array(
'basePath' => BASEPATH,
'baseUrl' => SITE_URL,
'mediaPath' => '/var/www/vhosts/adigital.agency/media/public/',
'mediaUrl' => URI_SCHEME . 'media.adigital.agency/'
)
),
'adigital.agency' => array(
‘devMode’ => false,
'environmentVariables' => array(
'siteUrl' => SITE_URL
)
),
'dev.adigital.agency' => array(
'devMode' => true,
'environmentVariables' => array(
'siteUrl' => SITE_URL
)
)
);
Through setting this up we found that the order of these array does also matter. For if you are on dev.adigital.agency, you are matching the dev.adigital.agency and the adigital.agency parts of the arrays. If the live site array is last then your variables for the dev site will be overwritten by the live ones. Think of your arrays as a check on if the domain contains the key, rather than an exact match.
Also you will see that we have set the siteUrl parameter for each domain separately instead of in the catch all. This is done because when we build a site with multi lingual support we expand this variable into an array for each language to create our siteUrls. What we also like to do in our Craft builds is to move the plugins, templates, and translations folders to sit below the root outside of craft. We do this so that we can track them separately between our dev and live sites and also version control them across both environments using git branches. To do this just add the following to your index.php file:
define('CRAFT_PLUGINS_PATH', realpath(dirname(__FILE__) . "/plugins").'/');
define('CRAFT_TEMPLATES_PATH', realpath(dirname(__FILE__) . "/templates").'/');
define('CRAFT_TRANSLATIONS_PATH', realpath(dirname(__FILE__) . "/translations").'/');
Once you have adjusted the config, db, and index files and added in your environment variables such as folder paths and database connection details, you should be all done. Just follow the craft docs and add these variables to your settings pages in the backend of Craft and you should be able to test the domains. Whilst we keep the craft cms files above the root so that they are outside of git, we have had some debates around if the cms should be held within git or not.
Currently we keep the cms outside of git because with craft, one click updating means that we have no need to ever keep track of these files. For this reason we have moved the folder we will be working on into our domains folder and all of the files in version control are from the domain root folder downwards. Switching between dev and live branches is kept very simple as a result because all of the files are in the same location and folder structure locally on your machine.
Implementing it into ExpressionEngine
If you have looked into creating an ExpressionEngine multi-env configuration, then you will know that it is not native to the CMS out of the box, and there are a number of plugins available to achieve the desired effect. Here at A Digital though we found some of these plugins to be quite verbose and difficult for us to manage. There seemed to be a long setup process so we decided to create our own solution. We wanted to change the default config and database files as little as possible so that we are still able to update our cms going forwards.
To keep the changes minimal, we have just added an include into the bottom of each of these files. The files we are including are going to override our config variables with our own settings and we are using this approach on a number of our sites now. The first step is to get the server name and run it through a switch case, this allows us to set variables per environment. However, php can handle a few settings for us dynamically also by using variables such as $_SERVER[‘DOCUMENT_ROOT’] to get our base_path.
The main difference between our approach and that of others though is that we do not need to create an array of upload directory ids with new filepaths for each environment. This felt very static and easy to forget about if we were to create a new upload directory in the future. To make this more dynamic we have added a query to loop through each of our upload directories and set their values in the upload_preferences array variable. To gain database access at this point we are including our own db.php file which sets the db connection settings based upon our environment.
// include our multi_env/db.php file
include('db.php');
// Create connection
$conn = new mysqli('localhost', $db['expressionengine']['username'], $db['expressionengine']['password'], $db['expressionengine']['database']);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT * FROM exp_upload_prefs;";
$result = $conn->query($sql);
$folder_path = "/assets/uploads";
if ($result->num_rows > 0) {
$directories = array();
// output data of each row
while($row = $result->fetch_assoc()) {
$directory_path = explode($folder_path, $row["server_path"]);
$directory_url = explode($folder_path, $row["url"]);
if (strpos($row["url"], 'cdn.') !== false) {
$url = $row["url"];
} else {
$url = "https://cdn.adigital.agency/uploads".$directory_url[1];
}
if (strpos($row["server_path"], '_cdn') !== false) {
$path = $row["server_path"];
} else {
$path = "/var/www/vhosts/adigital.agency/subdomains/_cdn/public/uploads".$directory_path[1];
}
$directories[$row["id"]] = array(
"name" => $row["name"],
"server_path" => $path,
"url" => $url
);
}
$config['upload_preferences'] = $directories;
}
In creating our own approach to an ExpressionEngine multi-env conf we have tried to keep it very lightweight without any unnecessary settings. If we need to set additional variables then we can just add them straight into our multi_env config file. One of the main variables we use though in our templates is site_env. We set this in our switch case to be live, dev, training, whatever our subdomain is called. We can then use this in our global layout file in a {if site_env == “live”} conditional. This means that we can add features such as tracking, heat mapping, etc to only the live site as we would not need any of this data to be collected for our dev site.
Git deploy with multiple environment configurations
Obviously you will need to copy your files into the respective subdomain folders, setup your dns records, and also create and populate a database for each subdomain, but the cms configuration is all done for you now. To ease the process of future updates we use git deployment. This means that when we update a file on our dev site, we do not need to then upload it to each of our subdomain environments and the live site. We can just a click a button and it is all handled for us.
To do this we create branches from our live sites git repository. We make a branch for each environment that we can safely work in. The next step we take is to then set up git deployment for each of these branches, we still allow FTP to the dev site for fast uploading and testing when building custom plugins, but a push will also publish files for us so that we do not miss any.
We recommend storing the CMS above the route on the server so that it is outside of git. Your branches for dev and live can then contain the files that sit below the root for each of these domains. This decreases the number of files held in git and makes branch merges much cleaner in our view. This is optional however and the choice is ultimately up to you if you wish to include the cms in your git repository.
When we are ready to go live with some changes, we simply merge the dev branch into the live branch, and then go to our deployment tool and press the deploy button. We do not touch the live environment via FTP as we handle this fully with merge deployments from dev. Currently for deployment we are using a tool called deploybot which can be found at https://deploybot.com/, but there are many others also available out there.
How it all affects our Development workflow
By working with our multiple environments and git deployment, we have an area of freedom and flexibility to be able to make changes at will to a site without any fear of affecting traffic or sales. By working on a dev environment we are ensuring that the changes will only go live when both the client and our team are happy and ready to make the update.
Here at A Digital we find it very easy to just merge a git branch and click the deploy button, it keeps our work separate so we do not accidentally connect to the live site instead of the dev in our FTP settings. This gives us the confidence to just jump in and start coding on the dev site.
We also share our dev environment with our clients so that they can see the changes next to the live site and any tweaks can be applied before we go live with it, this means that once it is live, that piece of work is complete. For something that is relatively easy to setup, it has increased our productivity and allowed us to set up various use cases to try multiple approaches.
Clients love to play about with new features before they go live as they feel like they are kept in the loop with our development process and they now have the opportunity to be confident in the work before it is pushed to the live site, rather than coming back with changes to the live site that are a priority. It is much more relaxed and there is less urgency as their site does not need fixing at any point, it is a seamless transition.
If you read our blog post about why clients need a multiple environment configuration, then you will know that we have also seen large speed benefits through sites such as pingdom simply by putting our assets on a cookieless subdomain. If you are not yet using a multiple environment configuration, then we will strongly urge you to give it a try and see how you get on with it. It can help with your development process and also give clients confidence about the updates they will be receiving.
Matt Shearing
Matt develops custom plugins for Craft CMS to extend the core functionality. He also focusses on pagespeed improvements and server environments.