Multi Environment Deployment with CI/CD

Anuraag Jain
The Startup
Published in
3 min readAug 14, 2019

Automating your app deployment helps you to focus and eliminate the redundant steps. Sometimes your app has to have multiple environments for various reasons. Let us consider three environments in our case: development, staging, and production with environment-specific sensitive data.

To demonstrate how to setup multi-env deployment I would consider the following things:

  • ExpressJS project or anything of your choice.
  • Gitlab

Your Project

In your project, there must be a place to save your environment-specific files. In NodeJS I’m going to use dotenv NPM package for it. Create three files .env.development , .env.staging , .env.production which will have environment-specific values.

In your app.js file or project startup file you must be load the env file (if required).

// existing code
const
dotenv = require('dotenv');
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });
...

In your local system you must specify the environment name when starting the project, something like this:

NODE_ENV=development nodemon ./bin/www

Gitlab

source: gitlab.com

Create a repo and push your code to Gitlab and then we are ready to work on the deployment process. You create a separate branch for every environment.

Tip: Never commit your environment files as they have sensitive data

Under Project’s Settings > CI/CD > Variables. Upload the sensitive data by encoding it and saving the private key too in the variable. Make sure you’ve given a unique name to the key here so that we can differentiate between the environments.

https://gitlab.com/<USERNAME>/<PROJECT-NAME>/-/settings/ci_cd

Now let’s create the deployment script. Create .gitlab-ci.yml file in the root directory of your project and paste the following code. I’ve selected ubuntu:latest as my docker image but you may choose anything according to your preference.

image: ubuntu:latestvariables:    WORK_DIR: ${CI_PROJECT_NAME}    BRANCH: ${CI_COMMIT_REF_NAME}

Now let’s add script for staging environment.

staging:  stage: staging  environment:     name: 'staging'  script:     - echo "$STAGING_ENV" > .env.staging.tmp     - # decode and rename the file to .env.staging     - # deploy to server     - # restart server  only:     - staging

We are creating a basic structure which is required for gitlab’s runner to understand our deployment procedure. The core logic is under script , where STAGING_ENV is the environment variable declared in gitlab ci/cd variables earlier. It can be accessed like any other variable in bash.

The sensitive data is stored in .env.staging.tmp and then we decipher the contents to obtain the actual keys and rename the file to .env.staging. Deploy the code to your remote server via SSH, SCP, RSync, etc., and restart the server to get the latest changes.

Note: We only run the stage staging where there is a change in that branch.

Finally paste the below code which enables the stage which we have just created

stages:  - staging

Your .gitlab-ci.yml should look something like below

image: ubuntu:latestvariables:  WORK_DIR: ${CI_PROJECT_NAME}  BRANCH: ${CI_COMMIT_REF_NAME}stages:  - staging
staging: stage: staging environment: name: 'staging' script: - echo "$STAGING_ENV" > .env.staging.tmp - # decode and rename the file to .env.staging - # deploy to server - # restart server only: - staging

Voila, you can repeat this process for production or test environment.

Sample code with Production & Staging branches

Checkout my repo to build & deploy your angular application from gitlab here.

You can use similar logic on Bitbucket Pipelines or any CI/CD for that instance to setup a multi-env deployment.

Do share your feedback by commenting below :)

--

--

Anuraag Jain
The Startup

Software Engineer | Ireland | Github/Twitter: @anuraagdjain.