Start free
Andrew Hanna

Andrew Hanna

How to Set Up a Salesforce CI/CD Pipeline With GitHub Actions

How to Set Up a Salesforce CI/CD Pipeline With GitHub Actions

To set up a Salesforce CI/CD pipeline with GitHub Actions, you store your metadata in Git in source format, authenticate GitHub to your org with a JWT-based Connected App, and add a workflow that validates changes on every pull request and deploys them on merge. The whole pipeline lives in a single .github/workflows YAML file and runs on the Salesforce CLI. This guide walks the current, sf v2 way to do it, then flags the parts that quietly break in production. It is part of our SF Guides library at /serpent/resources.

What do you need before you start?

A working pipeline assumes a few things are already in place:

  • Source-format project. Your metadata lives under force-app in source format, committed to a GitHub repo.
  • Salesforce CLI v2 (sf). The old sfdx CLI is deprecated, so build on sf.
  • A deployment target. A sandbox for validation and a production or staging org for release.
  • A Connected App in the target org with digital signatures enabled, plus a pre-authorized integration user.

If your metadata is not in Git yet, that migration comes first. We cover the branching and environment mechanics in our companion guide to building a Salesforce CI/CD pipeline with GitHub Actions.

How do you connect GitHub Actions to Salesforce securely?

Use the JWT Bearer Flow. It is headless, needs no interactive login, and is what Salesforce recommends for CI. Generate a key pair, upload the certificate to a Connected App, then log in from the runner with the private key.

  1. Create the key and certificate: openssl genrsa -out server.key 2048 then openssl req -new -x509 -nodes -sha256 -days 365 -key server.key -out server.crt.
  2. Create a Connected App in the org, enable Use digital signatures, and upload server.crt.
  3. Store server.key and your consumer key, username, and instance URL as GitHub repository secrets, never in the YAML.
  4. Authenticate on the runner: sf org login jwt --client-id $SF_CONSUMER_KEY --jwt-key-file server.key --username $SF_USERNAME --instance-url $SF_INSTANCE_URL --set-default-org.

What does the GitHub Actions workflow look like?

The core pattern is one workflow with two behaviours, keyed off the event. Validate on a pull request so nothing merges that would fail; deploy for real when the change lands on your main branch.

  • Triggers: pull_request against main for validation, push to main for deployment.
  • Validate step (PR): sf project deploy validate --source-dir force-app --test-level RunLocalTests --wait 30 --verbose. This is a check-only run, so nothing is written to the org.
  • Deploy step (merge): sf project deploy start --source-dir force-app --test-level RunLocalTests --wait 30 --verbose.

Use an if: condition on each job so the same file handles both events. Running local tests on validation is what makes this a real gate rather than a rubber stamp, and it is the backbone of any serious Salesforce test automation pipeline in CI.

How do you deploy only what changed?

Deploying the whole force-app directory every time is slow and grows worse as the repo grows. The community tool sfdx-git-delta computes the diff between two commits and produces a package of only the changed metadata:

sf sgd source delta --to HEAD --from HEAD~1 --output delta --generate-delta --source-dir force-app

You then point the deploy command at the delta directory. It cuts deploy time sharply, but read the next section before you trust it blindly.

What quietly breaks a hand-rolled pipeline?

Most tutorials end at a green deploy. The failures show up later, and they are the reason we build tooling around this instead of leaving teams a raw YAML file:

  • Deletions. A naive delta deploys additions and edits but does not remove deleted components. You need a destructive-changes step, or metadata drifts and stale components pile up.
  • Partial metadata. Profiles, permission sets, and a few other types still live in large shared files that do not diff cleanly, so delta deployments can miss or clobber them.
  • Maintenance is yours. Every CLI change, new metadata type, and edge case is now your team's YAML to fix. That cost compounds, and it is exactly what back-promotion between environments makes worse if you script it by hand.

If you would rather not own that pipeline forever, a managed platform handles delta, destructive changes, and the partial metadata edges for you. See what Serpent covers and how it is priced before committing a quarter of engineering time to a homegrown one.

FAQ

Should I use sfdx or sf for a Salesforce GitHub Actions pipeline?

Use sf (Salesforce CLI v2). The legacy sfdx CLI is deprecated and should not be the base of a new pipeline.

Why use JWT auth instead of a username and password?

JWT Bearer Flow is headless and needs no interactive login or MFA prompt, which is what a CI runner requires, and Salesforce recommends it for automation.

What is the difference between deploy validate and deploy start?

Validate is a check-only run that verifies and runs tests without changing the org, ideal on pull requests; start performs the actual deployment on merge.

Do I need sfdx-git-delta?

No, but it is worth it once deploys get slow. Just pair it with a destructive-changes step so deletions are handled, not silently skipped.

Related Articles

Curious about faster shipping before you dive in? Let's talk

Commitment free!