Effective Deployment of Heroku Applications Using GitLab CI/CD
Written on
In a prior discussion, we examined automating Heroku deployments via GitLab CI/CD, which enabled app deployment to the production environment whenever code was pushed to the main branch.
In this article, we will delve into a more sophisticated method: handling multiple environments. Most development teams maintain at least three environments: local development, staging, and production.
Additionally, some teams implement a Gitflow branching strategy, utilizing both dev and main branches. While this strategy has become less popular, it remains prevalent in many organizations.
Today, we will detail how to set up GitLab CI/CD to deploy our application to the staging environment upon pushing to the dev branch, and to the production environment when pushing to the main branch.
Getting Started
Before proceeding, ensure you have both a Heroku account and a GitLab account.
Heroku serves as an excellent platform for hosting and deploying your applications. As a Platform as a Service (PaaS), it allows you to concentrate on development while minimizing infrastructure complexities. You can create a Heroku account [here](https://www.heroku.com).
GitLab provides a robust solution for managing your code. It not only functions as a source code management tool but also includes built-in CI/CD capabilities, enabling the setup of pipelines to test and deploy your code without relying on external tools. You can create a GitLab account [here](https://gitlab.com).
The demo application illustrated in this article utilizes both GitLab and Heroku, with the complete code available in the GitLab repository [here](https://gitlab.com).
Running Our App Locally
To run the application locally, clone the repository, install the dependencies, and execute the start command. In your terminal, execute the following commands:
$ git clone https://gitlab.com/tylerhawkins1/heroku-gitflow-staging-production-gitlab-cicd-demo.git $ cd heroku-gitflow-staging-production-gitlab-cicd-demo $ npm install $ npm start
After launching the application, navigate to http://localhost:5001/ in your web browser to see the application running locally:
Deploying Our App to Heroku
Now that the application is running locally, let’s deploy it to Heroku for broader accessibility beyond your local machine.
We will deploy the application to both a staging and a production environment, resulting in two Heroku applications linked to the same GitLab repository.
If you haven’t already, install the Heroku CLI on your machine before proceeding.
Once the Heroku CLI is installed, execute the following commands from your terminal to switch to the main branch, create a new production Heroku app, deploy it, and then open it in your browser:
$ git checkout main $ heroku create heroku-gitlab-ci-cd-production --remote heroku-production $ git push heroku-production main $ heroku open --remote heroku-production
You should now see the same application, but hosted at a Heroku URL instead of on localhost. Congratulations—you have successfully deployed your Heroku application to production!
Next, we must configure and deploy your staging application. To accomplish this, run the following commands:
$ git checkout dev $ heroku create heroku-gitlab-ci-cd-staging --remote heroku-staging $ git push heroku-staging main $ heroku open --remote heroku-staging
You will now have the same application deployed to a different URL, which serves as your staging environment. Both environments are now configured!
Observe the differences and similarities between the commands for the production and staging applications:
- The production application uses the main branch, while the staging application uses the dev branch.
- The production application is named heroku-gitlab-ci-cd-production, while the staging application is named heroku-gitlab-ci-cd-staging.
- The production application’s git remote is labeled heroku-production, and the staging application’s git remote is labeled heroku-staging.
- Both applications’ git remotes utilize the main branch since Heroku deploys the app when code is pushed to its main branch.
Remember that the main branch mentioned here differs from your own set of main and dev branches. The main branch you are pushing to is the one on your git remote, which is Heroku in this case.
So when you are on your local main branch and execute git push heroku-production main, you are pushing your main branch to the Heroku production app’s main branch. Conversely, when you are on your local dev branch and run git push heroku-staging main, you are pushing your dev branch to the Heroku staging app’s main branch.
Making Changes to Our App
With our Heroku app operational, what if we want to implement some changes? Following a simplified version of the Gitflow branching strategy, we could do the following:
- Switch to the dev branch.
- Make changes to the code.
- Add, commit, and push those changes to the dev branch.
- Deploy those changes to the staging Heroku app using git push heroku-staging main.
- Switch to the main branch.
- Merge the dev branch into the main branch.
- Deploy those changes to the production Heroku app using git push heroku-production main.
(If we were strictly adhering to Gitflow, we would have created a feature branch to merge into dev, and a release branch to merge into main, but we will skip those steps for simplicity.)
Wouldn’t it be convenient to automate the deployment to either of our environments instead of doing it manually each time?
This is where GitLab CI/CD comes into play.
Continuous Integration / Continuous Deployment
Continuous integration (CI) focuses on frequently committing changes and maintaining a stable build at all times. Typically, this is validated by running checks in a CI pipeline, which may include linters, unit tests, and/or end-to-end tests.
Continuous deployment (CD) emphasizes frequent deployment. If the checks in the CI pipeline succeed, the build is deployed. If any checks fail, the build does not get deployed.
With GitLab CI/CD, we can set up our CI pipeline to execute tests and subsequently deploy our app to Heroku if all tests pass. Crucially, we can define rules within our CI pipeline to specify the deployment environment.
Configuring GitLab CI/CD
We can programmatically create a CI pipeline in GitLab using a .gitlab-ci.yml file. Here’s how our file looks:
image: node:20.10.0
cache:
paths:
- node_modules/
before_script:
- node -v
- npm install
stages:
- test
- deploy
unit-test-job:
stage: test
script:
- npm test
deploy-job:
stage: deploy
variables:
HEROKU_APP_NAME: $HEROKU_APP_NAME_PRODUCTIONrules:
if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
variables:
HEROKU_APP_NAME: $HEROKU_APP_NAME_PRODUCTION
if: $CI_COMMIT_REF_NAME =~ /dev/
variables:
HEROKU_APP_NAME: $HEROKU_APP_NAME_STAGING
script:
- apt-get update -yq
- apt-get install -y ruby-dev
- gem install dpl
- dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_API_KEY
GitLab CI pipelines consist of stages and jobs, with each stage potentially housing multiple jobs. Our pipeline includes two stages: test and deploy. The test stage runs our unit tests in the unit-test-job, while the deploy stage handles the deployment to Heroku in the deploy-job.
We can only move to the next stage if all jobs in the preceding stage pass. Thus, if the unit tests fail, the app will not be deployed, which is desirable—we don’t want to deploy an unstable application.
The deploy stage contains a rules section that implements conditional logic to determine the deployment environment. If we are on the main branch, the production app is deployed; if we are on the dev branch, the staging app is deployed.
The deploy stage also refers to several variables such as $HEROKU_APP_NAME_PRODUCTION, $HEROKU_APP_NAME_STAGING, and $HEROKU_API_KEY. These are defined as CI/CD variables within GitLab.
If you are setting this up in your GitLab account, you will first need to locate your API key in your Heroku account. Within the Heroku account settings, there should be a section for your API key. If you haven’t generated one yet, create one now.
Next, within your GitLab project, navigate to Settings > CI/CD > Variables. Expand that section and add three new variables:
- The value for $HEROKU_API_KEY should be the API key from your Heroku account.
- The value for $HEROKU_APP_NAME_PRODUCTION should reflect the name of your production Heroku app. My production app is named heroku-gitlab-ci-cd-production, but your app will have a unique name.
- The value for $HEROKU_APP_NAME_STAGING should represent the name of your staging Heroku app. Mine is heroku-gitlab-ci-cd-staging, but similarly, your name will differ.
With this configuration, your GitLab CI pipeline is set up and ready to test.
Deploying Our Heroku App to the Staging Environment
Now, let’s switch to the dev branch and modify some code in our application. I made a minor adjustment to the heading text and added several new lines to the UI. You can perform a similar modification.
Next, add, commit, and push that change to the dev branch. This action will initiate the GitLab CI pipeline, and you can monitor its progress in real time within GitLab.
If everything proceeds smoothly, you should see both the test and deploy stages successfully complete. Now, check the URL of your hosted Heroku app in the staging environment. The GitLab CI pipeline has managed the deployment for you, so you should observe your changes reflected live in the staging environment!
With a staging environment established, you now possess an ideal space for manual testing of your code in a hosted setting. This area is also perfect for QA testers or product managers to validate changes prior to production deployment.
Next, review the production app URL. It should still display the previous UI without the latest updates, as the GitLab CI pipeline only deployed the changes to the staging environment, not the production one.
Having confirmed that the code is satisfactory in the staging environment, let’s promote it to the production environment.
Deploying Our Heroku App to the Production Environment
Let’s switch to the main branch and merge the dev branch into your main branch. This can be done via a pull request or through the command line with git merge dev followed by git push.
This action will once again trigger the GitLab CI pipeline, this time preparing to deploy your production application.
You can also track all pipeline executions on the Pipelines page in GitLab, where you can view the various builds for your dev and main branches:
Once the pipeline has completed, visit your production Heroku app's URL. You should now see your changes live in production. Great job!
Conclusion
A well-configured CI pipeline enables rapid and confident deployment of new features without the need for manual intervention in the deployment process. By establishing multiple environments for local development, staging, and production, you gain enhanced control over when and where your code is released.
Together, Heroku and GitLab CI/CD streamline these processes, making your DevOps practices significantly more efficient!