There is one thing I love most about SFDX: The possibility to automatically rollback, if your continuous integration (CI) pipeline fails. This greatly decreases the probability for your developers to break your build pipeline. As a result, they are less likely having to pull the andon cord.

When I first introduced CircleCI, I had no idea how easy it would be to implement an automatic rollback with SFDX. I still remember the pre-SFDX era where one would have to build the destructiveChanges.xml and perform the rollback manually. This is tedious and integrates badly with modern deployment solutions.

The much better solution is a modern CI platform. All major CI vendors offer the functionality to perform actions on fail. This makes an automatic rollback a piece of cake. I decided to build ours using only out-of-the-box features from CircleCI and the Salesforce (the SFDX CLI).

Ingredients Of A Rollback With SFDX in CircleCI

With SFDX, a rollback is as simple as re-installing the previously installed package version in the same job. Check out my previous (German) blog posts how to architect your repositories for CI and how to setup CircleCI with the 1-1-1 template. If you haven’t adoped package based deployment yet, now is a good time to start.

In a nutshell, you need to include the following steps for all jobs that deploy on a persistent environments:

  1. Retrieve the currently installed package version for the target org and store it.
  2. Build a new package version and install it.
  3. Perform additional optional deployment steps.
  4. Execute a full test run on the target org.

If any of these steps fail, execute the automatic rollback by reverting to the previously installed package version. We are using this pattern for more than half a year now, and it works around 95% of the time.

Benefits Of An Automatic Rollback

In one recent instance we had badly designed tests that caused the execution time of the full test run to go through the roof. The test run timed out, causing the step to fail. This triggered the rollback, reverting to the old package version. This is highly desirable, because otherwise the defective package version would stay on Staging, effectively breaking our CI pipeline for all other packages (our team actively develops around 10 packages).

A staging CI job that rolled back due to test step timing out.

Retrieving The Currently Installed Package Version

I found the Tooling API to be the easiest way to identify the currently installed package version for rollback. We query our DevHub for the SubscriberPackageId, then query the target org for the currently installed package version and finally store it in the project directory for later reference (you could also export it as an environment variable).

- run:
  name: Retrieve Originally Installed Package Version For Rollback
  command: |
    output=$(sfdx force:data:soql:query -t -q "SELECT SubscriberPackageId FROM Package2 WHERE Id = '$PACKAGE_ID'" -u $USERNAME_PRODUCTION)
    SUBSCRIBER_PACKAGE_ID=$(echo $output | grep -o '033[a-zA-Z0-9]*')
    output=$(sfdx force:data:soql:query -t -q "SELECT SubscriberPackageVersionId FROM InstalledSubscriberPackage WHERE SubscriberPackageId = '$SUBSCRIBER_PACKAGE_ID'" -u $USERNAME_STAGING)
    SUBSCRIBER_PACKAGE_VERSION_ID=$(echo $output | grep -o '04t[a-zA-Z0-9]*')
    if [ -z $SUBSCRIBER_PACKAGE_VERSION_ID ]; then
      echo "No installed version found. Rollback disabled."
    else
      echo "Storing originally installed package version for rollback: $SUBSCRIBER_PACKAGE_VERSION_ID ..."
      echo $SUBSCRIBER_PACKAGE_VERSION_ID >> rollback_package_version_id.txt
    fi

Configure Your CI Pipeline To Rollback On Fail

Now all we have to do is configure an on_fail step in our CI job. If somthing fails, this step is executed. This will do the actual rollback, installing the previously stored package version in the target org.

- run:
  name: Rollback To Previous Package Version
  command: |
    ROLLBACK_PACKAGE_VERSION=rollback_package_version_id.txt
    if [ -f "$ROLLBACK_PACKAGE_VERSION" ]; then
      read -r original_package_version < $ROLLBACK_PACKAGE_VERSION
      echo "Rolling back to original package version ... $original_package_version"
      sfdx force:package:install -w 10 -b 10 -u $USERNAME_STAGING -p $original_package_version -k $INSTALLATION_KEY -r
    else
      echo "No package version was installed. Rollback disabled."
   fi
 when: on_fail

With the --upgradetype on force:package:install you can control if you want your rollback to be „soft“ (deprecateOnly) or „hard“ (Delete).

Putting It All Together

We only have to integrate these steps in the jobs that deploy on persistent orgs (that is: Sandbox and Production orgs). And we’re done: We now have a CI pipeline that performs an automated rollback using Salesforce out-of-the-box functionality with SFDX.

At The Mobility House, we use a three-stage pipeline that validates the full repository (scratch_org_test), deploys a beta version on our Staging (build_and_install_staging) and finally promotes the validated package and installs it on production (install_production).

A failing CI pipeline that with automatic rollback

I added the full CI config to my 1-1-1-setup-template repository. Feel free to use it to jump-start your SFDX projects.