Staging environments with ember-cli-deploy

ember-cli-deploy is the recommended and community agreed upon way to deploy your ember-cli applications. It comes with a lot of plugins that enables you to customize your deployments and is one of the top rated ember-cli-addons on ember-observer.

Here's a great video of @lukemelia and @grandazz explaining ember-cli-deploy's deployment pipeline in great detail.

To put a long story short, you should be using ember-cli-deploy to deploy your ember-cli applications to production.

Deploying to production for most teams is only half the story of their deployment workflow though. Most teams also deploy to a staging-environment that mirrors production as close as possible and is used to test new features internally before releasing to the public.

Kind of unfortunately ember-cli only supports three environments development, testing and production and does not know anything about staging-environments when running the ember build-command. In kind of a way this also makes sense because staging is not really a name for a pre-production environment that all teams in the world agree on. There are teams that name the pre-production environment pre-production or qa or teams that simply need to support multiple staging environments at once.

Despite no notion of staging, pre-production etc. as a deployment environment in ember-cli, you can still deploy these kinds of environments via ember-cli-deploy.

The difference between staging and production

As said before staging should mirror production as close as possible. It will be accessible via a different URL and will most likely use different hosts for your api-services and use different API-Keys for services that you use in your application (error-tracking-, mapping-services etc.). So basically what you want to do when deploying a 'production-like'-build to staging is to deploy a production-build that uses a different environment-configuration based on config/environment.js.

So how do we get ember-cli-deploy to do the right thing for us? We need to do the following:

  • instruct ember-cli-deploy to deploy to a different location than when deploying to production
  • we want to build the ember application mirroring the production-build
  • we want to use a different configuration from config/environment.js based on the environment we deploy to

Deploy Targets

When taking a closer look at the deploy.js-configuration file that ember-cli-deploy creates for you when installing via ember install ember-cli-deploy you see that also ember-cli-deploy does not concern itself with environments, it names these different stages of deployments deployTargets.

The deployTarget in ember-cli-deploy's configuration will be set based on the target for your deployment you pass to the ember deploy-command. Example:

# deployTarget 'production'
ember deploy production 

# deployTarget 'staging'
ember deploy staging

# deployTarget 'pre-production'
ember deploy pre-production  

You get the idea. The deployTarget will be available as an environment variable named DEPLOY_TARGET while executing a deployment.

Ember-cli-deploy uses the deployTarget to customize the deployment. For example you can use the deployTarget to change to which bucket the index.html-file ember-cli-deploy-build built for you will be uploaded to by ember-cli-deploy-s3-index based on the deployment environment you are deploying to:

// config/deploy.js

module.exports = function(deployTarget) {  
  var ENV = {
    // ...
    "s3-index": {
      accessKeyId: process.env.AWS_KEY,
      secretAccessKey: process.env.AWS_SECRET,
      bucket: "app.example.com",
      region: "eu-west-1"
    }

    // ...
  };

  if (deployTarget === 'staging') {
    ENV['s3-index'].bucket = 'app-staging.example.com';
  }

  return ENV;
}

Building your application

Before uploading your appplication assets somewhere you first have to build your ember application. When using ember-cli-deploy you will use the ember-cli-deploy-build-plugin to do that. The plugin does more of course but to put it simple it will run ember build for you at the right time in the deployment pipeline passing whatever environment you have configured in config/deploy.js. So you'd most likely try doing something like this to configure a staging-build:

// config/deploy.js

module.exports = function(deployTarget) {  
  var ENV = {
    // ...
    build: {
      environment: 'production' // the default
    }

    // ...
  };

  // don't do this    
  if (deployTarget === 'staging') {
    ENV.build.environment = 'staging';
  }

  return ENV;
}

This of course isn't wrong per-se but will lead to problems because other addons don't know anything about how your pre-production environments are called and won't add their dependencies in a production friendly way. For example ember-faker will add all its fake data to your staging build which will bloat your application and won't mirror your production build as you'd want staging to do.

So what we need to do for the build is to actually use the production-environment to build the application:

// config/deploy.js

module.exports = function(deployTarget) {  
  var ENV = {
    // ...

    // always build the production environment
    build: {
      environment: 'production' // the default
    }

    // ...
  };

  return ENV;
}

But you still need to pass staging to the build command as this is used to build your application's config/environment.js-file that will be included in the bootstrap index.html that you will serve to your users. Right?! No.

Changing environment.js

As it turns out it's actually pretty easy to build a correct 'production-like' staging build because building your application is based on three files: config/deploy.js, config/environment.js and ember-cli-build.js.

  • config/deploy.js - used to customize the deployment
  • config/environment.js - used to customize configuration settings like API-urls based on environment
  • ember-cli-build.js - Contains the build specification for Broccoli. Used for customizing the build of your application (customize fingerprinting, manually including js-dependencies etc.)

Ember-cli will create the environment-configuration it includes in your app's index.html based on the function exported in config/environment.js that gets passed the environment from the ember build-command.

So creating a 'production-like'-build with different configuration from 'config/environment.js' in index.html is actually pretty easy, you just have to change the checks for environment that gets passed into the function from config/environment.js from the passed-in environment to the deployTarget that ember-cli-deploy stores in the process.env.DEPLOY_TARGET environment variable when executing a deploy.

// config/environment.js
module.exports = function(environment) {  
  var deployTarget = process.env.DEPLOY_TARGET;
  var ENV = {
    modulePrefix: 'example',
    //...
  };
  // ...
  if (deployTarget === 'staging') {
    ENV.apiServer = 'https://api-stg.example.com';
  }

  if (deployTarget === 'production') {
    ENV.apiServer = 'https://api.example.com';
  }

  return ENV;
};

And that's it. With that setup you can deploy to multiple 'production-like'-deployTargets and customize their configurations and deployments quite easily. 🚀

Thanks for reading and as always just drop me a line on twitter or on the ember-slack channel (@LevelbossMike) if you have questions. I am also available for consulting work and would be happy to help you and your company with your ember projects.

Thanks to the rest of the ember-cli-deploy-team 👋 for a great deployment pipeline and @pangratz for valuable discussions on this topic 🍻

comments powered by Disqus