I have the following CI flows setup in my git repo:

  • Deploy to Staging org when master branch is updated (automated)
  • Deploy to Production org from master branch as initiated by Release Manager (manual)

The CI scripts use package.xml to facilitate deployments. How can I ensure that deployments to Production contain only the modified components and not everything that's been developed up until now -in a streamlined manner? The challenge I'm facing is best explained with an example:

Sprint 1

  1. Developer creates branch, creates CompA and adds it to package.xml, then merges branch to master
    • CI kicks off, CompA gets deployed to Staging org
  2. Developer creates branch, creates CompB and adds it to package.xml, then merges branch to master
    • CI kicks off, CompA and CompB get deployed to Staging org
  3. Release Manager manually deploys from master to Production org
    • CompA and CompB get deployed to Production org

Sprint 2

  1. Developer creates branch, creates CompC and adds it to package.xml, then merges branch to master
    • CI kicks off, CompA and CompB and CompC get deployed to Staging org
  2. Release Manager manually deploys from master to Production org
    • CompA and CompB and CompC get deployed to Production org

How do you recommend I configure my CI scripts so that in Sprint 2 only the new CompC gets deployed to Staging and Production?

  • When you say "scripts" what are you using? My initial gut is to clear out package.xml after the production deployment. Whether that's manual or automated Nov 1, 2019 at 2:16
  • I'm using Pipelines, but automation scripts apply to just about any CI/CD tool.
    – Mossi
    Nov 1, 2019 at 5:33

2 Answers 2


Script Level

There's essentially a couple of steps involved to this process of doing a delta deployment.

The first is to identify the changed components. If you're working with Git and GitLab Pipelines, this is not difficult. Given a start and an end commit, it's just

git diff start-commit end-commit --name-only --diff-filter=ACM 

to get a newline-separated list of path names that are changed. (Note: this does not include handling of deleted components. If you want to automatically handle component deletion, there'll be some additional logic involved, as those end up in a destructiveChanges.xml package).

The second step is, given that flat list of file names and your source tree, to build a Metadata API manifest (package.xml) and deployment ZIP file to push only the changed metadata components.

When I've done this in the past (with GitHub Pipelines), I used ForceCLI as the deployment engine because it makes it easy to do this step by piping in the results of the above git diff:

git diff start-commit end-commit  --name-only --diff-filter=ACM | force push -f -

force push -f takes that flat list of names and synthesizes a package.xml and deployment ZIP, then deploys it to your authenticated target org.

If you're running this script on every commit, start-commit can be HEAD~1 and end-commit can be HEAD. Otherwise, see below for Pipelines configuration.

Other Pieces

Disclaimer: I haven't worked with this tooling in about a year.

When I was building out delta deployments I found that I needed to write some shell to manage deployments of components that are multi-file; that is, static resources and Lightning components. I did this with sed by squashing all references to Lightning component files to the base component directory, and by zipping up static resources and likewise squashing references to the base .zip for that static resource.

I think the former has been fixed in ForceCLI; I don't know about the latter. I'd recommend doing some experiments.

Process Level

For production deploys, it's common that you won't be deploying one commit at a time, and that your release manager will wish to specify a commit range for this deployment spanning a sprint's work. Likewise, it probably won't be fired automatically as with staging, but will be manually invoked by the release manager.

For that use case, I configured variables in GitLab Pipelines for the start and end commit (and altered the git diff commands shown above to reference those variables). They defaulted to HEAD~1 and HEAD. However, when the Pipeline for production is run manually by the release manager, they have the option to populate those commit SHAs to identify a specific range of changes to deploy:

enter image description here

Source Code

No warranty, not under active development, etc.


The CI definition is mostly boilerplate and works the same as an SFDX CI setup, with JWT authentication as described in the docs.

Note that this is only set up for manual deployments, not automated staging pushes, but that could easily be added.

It's got two manual jobs in it, so once the pipeline is created, the validate (check-only) job can be kicked off first, followed by the deploy step. This was for demonstration purposes and probably won't mirror what you'd want to do in production.

image: circleci/python:3.6.4-node

.core-job: &corejob
  stage: deploy
  when: manual
    - curl https://force-cli.heroku.com/releases/v0.25.0/linux-amd64/force > force
    - sudo mv force /usr/local/bin/force
    - sudo chmod +x /usr/local/bin/force
    - openssl aes-256-cbc -k $KEY -in assets/server.key.enc -out assets/server.key -d -md sha256
    - force login --connected-app-client-id $CONSUMERKEY -u $USERNAME -key assets/server.key
    - source assets/deploy.sh $START $END

    - rm assets/server.key

  <<: *corejob
    CHECK_ONLY: "-c"

  <<: *corejob
    CHECK_ONLY: ""


This is the shim script I used to wrap ForceCLI and run the actual deployment. The manual handling of the static resources and Lightning components may not longer be necessary based on continued development of that tool.

# deploy.sh
# Use Force.com CLI and zip to compress static resources and perform delta deployment.
# David Reed, January 2019

# Compress unpacked static resources
for file in src/staticresources/* ; do 
  if [[ -d "$file" && ! -L "$file" ]]; then
    cd $file
    zip -r ../`basename "$file"`.resource *
    cd ../../..

# Delta deploy items changed between our start and end commit
# We use `sed` to squash all references to Aura bundle components and static resources to their parent components
# `sort -u` ensures that we only have a single reference to each component.
git diff $1 $2 --name-only --diff-filter=ACM | grep "^src" | sed -E 's#^src/aura/([^/]+)/.+#src/aura/\1#' | sed -E 's#^src/staticresources/([^/]+)/.+#src/staticresources/\1\.resource#' | sort -u | force push $CHECK_ONLY -l RunLocalTests -r -f -
  • Very nice! Upvoted. Interested to hear experiences of others too.
    – Mossi
    Nov 6, 2019 at 5:06
  • Very detailed. However I would suggest to OP that just deploying all metadata instead of just changed metadata is probably easier and will cut down on dependency errors Jul 13, 2020 at 15:06

We've written a SFDX Plugin called sfdx-git-packager which given two git refs, will generate an incremental metadata package. It deals with many (but not all) edge cases around building an incremental (delta) package.

We're able to do 90% of our deployments via CI with this tool.

See the readme for more information on how to use it and how it works.

This document outlines how to setup an incremental CI (on bitbucket, but concepts transfer to any other CI).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .