Close

Learn Continuous Deployment with Bitbucket Pipelines

Sten Pittet headshot
Sten Pittet

Contributing Writer

We'll see in this guide how you can implement a continuous deployment pipeline with Bitbucket Pipelines

Time

30 minutes

Audience

You are new to continuous deployment and/or Bitbucket Pipelines

In software development, you often have to do hard tradeoffs when developing an application. If you want to go faster, the general belief is that you will have to trade on the quality of your releases. But there's a development practice that can allow you to save time while releasing faster, and that is continuous deployment.

With continuous deployment, you remove the stress of deploying software by making it an automated process. Your development team doesn't have to stop anymore and switch context for a release – code is shipped to your customers as soon as a developer has finished their work.

We'll see in this guide how you can implement a continuous deployment pipeline with Bitbucket Pipelines.

Continuous delivery vs. continuous deployment

Continuous delivery is the practice of making sure that your code is always ready to release even if you are not deploying every change to production. It is recommended to update your production as often as possible to make sure that you keep the scope of the changes small, but ultimately you're in control the rhythm of your releases.

In continuous deployment, new changes pushed to the repository are automatically deployed to production if they pass the tests. This puts more emphasis (read: pressure) on your testing culture, but it's a great way to accelerate the feedback loop with your customers.

Diagram showing the difference between continuous deployment and continuous development | Atlassian CI/CD

Setting up a continuous deployment pipeline

In this example, we will extend the simple node.js app web built-in by adding a continuous deployment pipeline. Continuous deployment is a great way for teams to accelerate development. It removes the impediments related to the release approval process, and it allows developers to get feedback from customers as soon as they're done with their work. Issues are easier to identify and fix, and there's less context switching as there's no release time anymore.

Configuring a continuous deployment pipeline with Bitbucket Pipelines is very easy. We will see how to do it with a simple Hello World application that goes through a staging environment and acceptance tests before getting released automatically to production.

Step 1: Create a new Heroku app

On the continuous delivery tutorial, we pushed a branch with merges to the production Heroku app. We’re going to create another Heroku app to prevent conflicts when pushing in this tutorial. Run the following.

heroku create --remote production2
git push production2 main

Now, git remote -vv shows something like the following. Use staging, and production2 for the rest of this tutorial.

wmarusiak@C02F207NML7L cdtutorial % git remote -vv
origin git@bitbucket.org:pmmquickstartguides01/cdtutorial.git (fetch)
origin git@bitbucket.org:pmmquickstartguides01/cdtutorial.git (push)
production https://git.heroku.com/fierce-basin-45507.git (fetch)
production https://git.heroku.com/fierce-basin-45507.git (push)
production2 https://git.heroku.com/limitless-headland-92324.git (fetch)
production2 https://git.heroku.com/limitless-headland-92324.git (push)
staging https://git.heroku.com/thawing-river-12585.git (fetch)
staging https://git.heroku.com/thawing-river-12585.git (push)

Step 2: Set up the pipeline

Our workflow will be straightforward:

  • Build the application.
  • Run tests on the build.
  • Deploy to staging.
  • Run some acceptance tests on staging.
  • Deploy to production.

You'll see that it's very easy to create the corresponding pipeline configuration. We'll start by adding our automatic deployment to the staging environment to make sure that it's happening properly on every push.

image: node:16

clone:
  depth: full

pipelines:
  branches:
    main:
      - step:
          name: deploy_to_staging
          script:
            - npm install
            - npm test
            - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/thawing-river-12585.git main

Make sure to replace the Git push URL for main with the staging URL from git remote -vv.

You'll notice that we're using a full clone to make sure that Heroku doesn't reject our push. Then we're also using a branch-specific pipeline to make sure that we only deploy staging for changes pushed on main and not on other branches. You can push this configuration to Bitbucket to see it in action.

Pushing configuration to Bitbucket pipelines

Automatic deployment to staging is completed

At this stage, we now have our Hello World application deployed on staging.

Screenshot of "Hello World" application deployed on staging

We can now move on to the next step and add our acceptance tests. Acceptance tests are here to make sure that our application behaves as expected in a production-like environment (staging). The goal is to remove uncertainties due to differences in configuration between the environment used to test the build and your production.

If you look at the code of our app, our test is doing something extremely simple – it's looking for the presence of the string "Hello World" in the page. In a real application we recommend having acceptance tests that go much further and verify that all the underlying services used by your application (database, cache, third party, etc.) are working properly.

To add an acceptance test complete the following steps.

mkdir acceptance-test
touch acceptance-test/test.js

Then edit acceptance-test/test.js and add the following code snippet.

var request = require('supertest');

// Running a test on our staging environment
describe('GET /', function() {
  it('displays "Hello World!" on staging', function(done) {
    var staging_url = 'https://' + process.env.HEROKU_STAGING + '.herokuapp.com'
    // The line below is the core test of our app.
    request(staging_url).get("/").expect("Hello World!", done);
  });
});

Update the package.json file to include --timeout 10000.

{
  "name": "cdtutorial",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "mocha --exit --timeout 10000"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@bitbucket.org/pmmquickstartguides01/cdtutorial.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://bitbucket.org/pmmquickstartguides01/cdtutorial/issues"
  },
  "homepage": "https://bitbucket.org/pmmquickstartguides01/cdtutorial#readme",
  "dependencies": {
    "express": "^4.17.3"
  },
  "devDependencies": {
    "mocha": "^9.2.2",
    "supertest": "^6.2.2"
  }
}

So let's add our test right after our deployment to staging.

image: node:16

clone:
  depth: full

pipelines:
  branches:
    main:
      - step:
          script:
            - npm install
            - npm test
            - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/thawing-river-12585.git main
            - HEROKU_STAGING=thawing-river-12585 npm test acceptance-test

Make sure to replace the Git push URL for main with the staging URL from git remote -vv.

After pushing this new configuration to Bitbucket we can see our pipeline completing successfully.

Pipeline running successfully in Bitbucket

Bitbucket Pipelines now runs acceptance tests after deploying to staging

All that is left now is to add our production deployment at the end to complete our continuous deployment pipeline.

image: node:16

clone:
  depth: full

pipelines:
  branches:
    main:
      - step:
          script:
            - npm install
            - npm test
            - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/thawing-river-12585.git main
            - HEROKU_STAGING=thawing-river-12585 npm test acceptance-test
            - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/limitless-headland-92324.git main

Make sure to replace the git push url for main with the staging url from git remote -vv, and the git push url for production with the production2 url from git remote -vv.

A final push to Bitbucket will take our code changes through all the pipeline, building and testing the code then deploying to production after successfully verifying that staging works. In this case the homepage has been updated with a different message to make sure that it would get deployed all the way to production.

Updated homepage message to confirm it's been deployed to production "Hello World! Straight to production!"

Our new message went to production as expected!

Finalized the continuous deployment pipeline in Bitbucket

Checking that the code went through the pipeline

It is important to stress the importance of having a good test suite, as well as having real-time monitoring in place before you go ahead and implement continuous deployment for your own repositories. From now on changes will go straight to production as soon as they're pushed to the main branch so it's all the more important to make sure that your automated tests check the critical parts of your application. Additionally, you'll need monitoring tools to detect changes that affect negatively your production instances whether it's a full breakage or a degraded service.

You can find the final source in the Bitbucket Cloud repository linked below.

Sten Pittet
Sten Pittet

I've been in the software business for 10 years now in various roles from development to product management. After spending the last 5 years in Atlassian working on Developer Tools I now write about building software. Outside of work I'm sharpening my fathering skills with a wonderful toddler.


Share this article

Recommended reading

Bookmark these resources to learn about types of DevOps teams, or for ongoing updates about DevOps at Atlassian.

Devops illustration

DevOps community

Devops illustration

DevOps learning path

Map illustration

Get started for free

Sign up for our DevOps newsletter

Thank you for signing up