How to Configure CI/CD for an Atlassian Forge App

Reading Time: 10 minutes

Continuous integration and Continuous deployment (CI/CD) automates the process of deploying code changes, helping software teams deliver updates more quickly and increasing reliability. It's an essential part of how most teams test and ship software today.

At Adadam, setting up CI/CD is one of the first things we do with a new project because we think it's important to be able to build and test our apps in a controlled environment. As we are all working remotely, CI/CD is a way to align our developers with stakeholders or with our customers, and with automatic pipelines this can be done almost in real-time. 

After some trial and error, we’ve developed a CI/CD workflow that works really well for our team. I’m writing this tutorial to help other developers in the Atlassian community replicate our process. Bitbucket Pipelines gives you an easy way to set up CI/CD directly from your code repository. If you’ve tried it, I’d love to hear what you think and what’s worked well for you.

Forge, Atlassian's cloud app developer platform, allows you to deploy apps directly to Atlassian's infrastructure instead of self-hosting. Forge also includes built-in environments and CLI commands for local development, staging, and deploying to production. 

In this tutorial you'll learn how to build an Atlassian Forge app from scratch and how to configure an entire Continuous Integration and Continuous Deployment workflow using Bitbucket Pipelines. 

Before you begin

This tutorial works across multiple Atlassian Cloud products: Jira, Confluence, Bitbucket, and Forge. All four of these tools interconnect to create the full end-to-end workflow:

  • Forge is where you'll build and host your app.
  • Confluence is where the Forge app will be installed.
  • Jira is where you'll set up a backlog of tasks to track your app's development work. You'll be able to see the results of your CI/CD pipeline's deployment directly on the issue. 
  • Bitbucket is where you'll manage version control and create your CI/CD pipeline.

Setting up the Jira project and Bitbucket repo

We'll use Jira Cloud to build a backlog of tasks allowing an entire team to collaborate on the app project. Linked to Bitbucket, Jira will ease the collaboration between developers, testers, scrum master, product owners and stakeholders.

In this tutorial we'll see the CI/CD magic happen with each code commit and push. 

If you want to follow along, create a new Jira project (the one in this example is named DEMO) and a Bitbucket repository similar to the one linked below.

Creating the project backlog in Jira Software Cloud

In the set-up shown below, there are 2 issues in the Jira project backlog:

  • DEMO-2 : Create the Forge App
  • DEMO-3 : Create the Bitbucket pipelines config

These tasks will be used to track our work and code commits. By linking Jira and Bitbucket Cloud, we will be able to see code commits, continuous integration results and continuous deployment results directly on each Jira task.

Building the Forge App

Start by following the Confluence Hello World tutorial in the Atlassian Developer documentation:  

If this is your first time using Forge, don't forget to install Node.js and Docker before installing Forge. See the Getting Started guide.

1/ Create your Forge app

Create an app based on the Confluence macro template.

Forge provides multiple environments where you deploy the app. This tutorial uses the CLI default, the development environment. Learn more about staging and production environments.

  1. Navigate to the directory where you want to create the app. A new subdirectory with the app's name will be created there.
  2. Create your app by running:
  3. forge create
    1. Enter a name for your app (up to 50 characters). For example, hello-world-app.
    2. Select the UI kit category.
    3. Select the confluence-macro template.
  4. Change to the app subdirectory to see the app files:
  5. cd hello-world-app

2/ Install your Forge app

To use your app, it must be installed onto an Atlassian site. The forge deploy command builds, compiles, and deploys your code; it’ll also report any compilation errors. The forge install command then installs the deployed app onto an Atlassian site with the required API access.

:info: Note, you must run the forge deploy command before forge install because an installation links your deployed app to an Atlassian site.

  1. Navigate to the app’s top-level directory and deploy your app by running:
  2. forge deploy
  3. Install your app by running:
  4. forge install
  5. Select your Atlassian product using the arrow keys and press the enter key.
  6. Enter the URL for your development site. For example, example.atlassian.net. (View a list of your active sites in the Atlassian administration).

Once the successful installation message appears, your app is installed and ready to use on the specified site. You can delete your app from the site by running the forge uninstall command.

3/ View your Forge app

With your app installed, it's time to see the app on a page.

  1. Edit a Confluence page in your development site.
  2. Open the quick insert menu by typing /your macro title on the page. Once you start typing the title /Forge app for, the macro with your name will appear in the quick insert menu.
  3. Select your macro from the menu to add it to the page.
  4. Publish the page.

Your hello world app is now installed into your development site. The app should display on the page like the image below.

Configuring Bitbucket Pipelines

Bitbucket Pipelines is an integrated CI/CD service built into Bitbucket. It allows you to automatically build, test, and even deploy your code based on a configuration file in your repository.

:info: Check out the Bitbucket Pipelines Getting Started guide

Continuous integration is the practice of frequently merging changes to the main branch, using build checks to test for potential issues before changes are deployed. Continuous deployment and continuous delivery describe the steps that follow, when the code changes are deployed to production. The difference between the two is that continuous deployment is completely automated.

:info: Learn more about continuous integration vs delivery vs deployment

1/ Add development dependencies for your Forge app

You will need to modify the package.json file of the Forge app to add the proper dependencies for Forge and other frameworks.

"devDependencies": {
    "@forge/cli": "^4.3.2",
    "@types/jest": "^27.4.1",
    "@types/node": "^16.0.0",
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0",
    "@typescript-eslint/parser": "^4.23.0",
    "eslint": "^7.32.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "typescript": "^4.0.0"
  },

Your package.json file should now look like this:

{
  "name": "confluence-macro-ui-kit",
  "version": "1.0.5",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "scripts": {
    "lint": "./node_modules/.bin/eslint src/**/* || npm run --silent hook-errors",
    "hook-errors": "echo '\\x1b[31mThe build failed because a Forge UI hook is being used incorrectly. Forge UI hooks follow the same rules as React Hooks but have their own API definitions. See the Forge documentation for details on how to use Forge UI hooks.\n' && exit 1"
  },
  "devDependencies": {
    "@forge/cli": "^4.3.2",
    "@types/jest": "^27.4.1",
    "@types/node": "^16.0.0",
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0",
    "@typescript-eslint/parser": "^4.23.0",
    "eslint": "^7.32.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "typescript": "^4.0.0"
  },
  "dependencies": {
    "@forge/ui": "1.2.1"
  }
}

2/ Configure Continuous Integration in your pipeline

In order to configure your pipelines with Bitbucket, you need to create a bitbucket-pipelines.yml file in the hello-world-app folder.

:info: Atlassian provides a validator tool where you can test your configuration.

Because a Forge app is based on Node.js, the base Docker image for your pipeline configuration should be Node.js as well. We will use a LTS version of Node.js, version 16.14.2.

image:
  name: node:16.14.2

We will now configure two parallel steps in the pipeline on the master branch:

  1. Build the app,
  2. Check code linting.

We will also use caches to reduce the running time of the pipeline. Two Bitbucket repository variables are needed to run Forge commands from the pipeline in order to login on the Atlassian account:

  • $EMAIL: the variable to store your Atlassian account email,
  • $TOKEN: the variable to store your Atlassian API token.

The bitbucket-pipelines.yml file should now look like this:

image:
  name: node:16.14.2
pipelines:
  # For MASTER, Build, Test, Code Linting and Deploy to staging only
  branches:
    master:
      - parallel:
        - step:
            name: Run Build
            caches:
              - node
            script:
              - cd hello-world-app
              - npm install
        - step:
            name: Check Code Linting
            script:
              - cd hello-world-app
              - npm install
              - node_modules/.bin/forge settings set usage-analytics false
              # EMAIL AND TOKEN are Bitbucket Repository Variables
              - node_modules/.bin/forge login -u $EMAIL -t $TOKEN
              - node_modules/.bin/forge lint
            caches:
              - node

So far, we've set up the Continuous Integration of your Forge app. Now let’s continue with the Continuous Deployment setup.

3/ Configure Continuous Deployment in your pipeline

Now we need to add a step in the pipeline configuration to deploy the Forge app to the staging environment. Deploying to the staging environment will be automatic—there will be no human interaction in this case.

  - step:
      name: Deploy to Staging
      deployment: staging
      caches:
        - node
      script:
        - cd hello-world-app
        - npm install
        - node_modules/.bin/forge settings set usage-analytics false
        - node_modules/.bin/forge login -u $EMAIL -t $TOKEN
        - node_modules/.bin/forge deploy -e staging
        - node_modules/.bin/forge install --site YOURSITE.atlassian.net* --product confluence --non-interactive -e staging
        # if installed, we need to run --upgrade option
        #- node_modules/.bin/forge install --upgrade --site YOURSITE.atlassian.net --product confluence --non-interactive -e staging

* Replace YOURSITE with the name of your site

We can also add a final step to deploy the Forge app to the production environment. Deploying to the staging environment will NOT be automatic—a click will be necessary to choose to deploy to the production environment.

  - step:
     name: Deploy to Production
     trigger: manual
     deployment: production
     caches:
       - node
     script:
       - cd hello-world-app
       - npm install
       - node_modules/.bin/forge settings set usage-analytics false
       - node_modules/.bin/forge login -u $EMAIL -t $TOKEN
       - node_modules/.bin/forge deploy -e production

The bitbucket-pipelines.yml file should now look like this:

########################################################
#         Adadam | Atlassian Forge CI/CD demo          #
#          Bitbucket Pipelines CONFIGURATION           #
########################################################
image:
  name: node:16.14.2
pipelines:
  # For MASTER, Build, Test, Code Linting and Deploy to staging only
  branches:
    master:
      - parallel:
        - step:
            name: Run Build
            caches:
              - node
            script:
              - cd hello-world-app
              - npm install
        - step:
            name: Check Code Linting
            script:
              - cd hello-world-app
              - npm install
              - node_modules/.bin/forge settings set usage-analytics false
              # EMAIL AND TOKEN are Bitbucket Repository Variables
              - node_modules/.bin/forge login -u $EMAIL -t $TOKEN
              - node_modules/.bin/forge lint
            caches:
              - node
      - step:
          name: Deploy to Staging
          deployment: staging
          caches:
            - node
          script:
            - cd hello-world-app
            - npm install
            - node_modules/.bin/forge settings set usage-analytics false
            - node_modules/.bin/forge login -u $EMAIL -t $TOKEN
            - node_modules/.bin/forge deploy -e staging
            - node_modules/.bin/forge install --upgrade --site YOURSITE.atlassian.net --product confluence --non-interactive -e staging
            # if installed no need to install again = NEED IMPROVEMENTS
            #- node_modules/.bin/forge install --upgrade --site YOURSITE.atlassian.net --product confluence --non-interactive -e staging
      - step:
         name: Deploy to Production
         trigger: manual
         deployment: production
         caches:
           - node
         script:
           - cd hello-world-app
           - npm install
           - node_modules/.bin/forge settings set usage-analytics false
           - node_modules/.bin/forge login -u $EMAIL -t $TOKEN
           - node_modules/.bin/forge deploy -e production

An important callout: As of right now, Forge apps can only be owned and managed by a single Atlassian user. This is why we're using your email and token to authenticate and authorize in the pipeline.

In the future, Forge plans to add better CI/CD support via deployment tokens (follow the roadmap here) and also allow multiple users to own a Forge app (https://trello.com/c/4pLKOPNA/4-multi-user-app-ownership). 

Letting the magic happen: commit and push

After writing the pipeline configuration, it's now time to add, commit and push your source code to a Bitbucket repository.

1/ First go back to the root folder where you created your app:

cd ..

2/ Create a Bitbucket repository and clone it. For example:

git clone https://dlauberton@bitbucket.org/adadamfr/adadam-forge-cicd-example.git

3/ Add the source code of your app using Git:

git add hello-world-app

4/ Commit your source code (and don’t forget to use the Jira Cloud issue key “DEMO-2…” in your commit message) 😉

git commit -m"DEMO-2: forge app created"

5/ Finally push your source code to the Bitbucket repository:

git push

The push command will trigger the pipeline and you will be able to follow the pipeline’s run directly in the Bitbucket Pipelines UI.

Now let's have some fun and change some code in the Hello World app. With your favorite editor, open the file ./hello-world-app/src/index.jsx and replace the word "world" by your name.

There is a small change we need to make after the first time the pipeline is run. Initially, the Hello World app was not installed in your Atlassian Cloud site and so we explicitly stalled it as part of the pipeline's script via this line:

node_modules/.bin/forge install --upgrade --site YOURSITE.atlassian.net --product confluence --non-interactive -e staging

Now that the pipeline has been run once, the app is installed in our site and doesn't need to be installed again. So we comment the line out the Deploy to Staging step.

# - node_modules/.bin/forge install --upgrade --site YOURSITE.atlassian.net --product confluence --non-interactive -e staging

Ready, Set, Go! :slight_smile: Commit your changes:

git commit hello-world-app/src/index.jsx -m"DEMO-2: forge app changes"

git commit bitbucket-pipelines.yml -m"DEMO-3: bitbucket pipeline improvements"

And push your changes to your Bitbucket repository:

git push

:partying_face: A new run in your Pipelines will be triggered automatically!

Now, edit the page you previously created to add the Hello World app to it. As you will see, you will have 2 different apps: one exists in the development environment, the other exists in the staging environment.

Here is a little video where you can see the magic happen  :tada:

Viewing commit, build and deployment results in Jira Cloud

Because we used a Jira Cloud issue key in our commit message, and because both sites (Jira Cloud and Bitbucket Cloud) are linked, we are able to see commits, builds, and deployment results directly on each Jira issue. This is truly a useful feature!

After opening the issue DEMO-2, and clicking on the commit link, all commits are listed as follows.

All deployments are also listed, just by switching to the proper tab.

In the Jira project, there is also a full Deployments view where all deployments are visible on a timeline. This helps project managers to visualize all deployments for each issue in a simple way.

Going further…

Now you have a working CI/CD pipeline for your Atlassian app, allowing you to quickly ship code changes so stakeholders and even customers can test it. As a next step, why not run automated tests on your app to be sure that everybody will get the same user experience? Adding continuous testing to continuous integration and continuous deployment will be the next major step. 

Cypress.io is a complete end-to-end testing framework and helps developers to set up and write tests, run tests automatically, and get records and reports after automatic tests run.  If you are curious and want to discover Cypress.io in depth, have a look at this article that I wrote related to a specific customer use case while working for iDalko.

Learn more about Damien’s work at Adadam, or connect on the developer community.