Salesforce CLI DevOps Playbook: Auth, Deploy, CI/CD Fixes That Actually Work

If you already installed Salesforce CLI but your pipeline still breaks, this playbook is your next step. It focuses on post-install workflows that fail most often: authentication, deploy strategy, test levels, and CI troubleshooting.

If you still need installation help, start with How to Install Salesforce CLI with NPM and keep How to Fix Salesforce CLI NPM Installation Errors open for common setup failures.

What should I configure right after installing Salesforce CLI?

Set a known CLI version, confirm plugin health, and verify you can run core commands in the same shell your scripts use. This removes “works on my machine” drift before you touch authentication or deploys.

Use this quick baseline:

# 1) Verify version and executable location
sf --version
which sf

# 2) Check plugin state
sf plugins --core

# 3) Optional: pin a known CLI version for reproducible CI
npm install @salesforce/[email protected] --global

Then store the chosen version in your CI config so local and pipeline behavior stay aligned.

How do I set up Salesforce CLI auth for local development and CI/CD?

Use interactive browser auth for local work, then use JWT-based headless auth in CI. Mixing both in pipelines is a common source of unstable deploy jobs.

For local development, browser auth is simplest:

sf org login web --alias devhub --set-default-dev-hub

For CI, JWT auth requires three things set up in Salesforce before the CLI command will work:

  1. Generate a self-signed certificate and private key (the private key must not have a passphrase):

    openssl req -x509 -sha256 -nodes \
      -days 365 -newkey rsa:2048 \
      -keyout server.key -out server.crt
  2. Create a Connected App in the target org:

    • Enable OAuth settings with the api and refresh_token scopes.
    • Upload server.crt as the digital certificate.
    • Pre-authorize the CI user’s profile under “Manage” > “Policies.”
  3. Store secrets in your CI platformSF_CLIENT_ID (the Connected App consumer key), SF_USERNAME, and the private key file content as encrypted secrets.

Then the CI auth command is:

sf org login jwt \
  --client-id "$SF_CLIENT_ID" \
  --jwt-key-file "$SF_JWT_KEY_PATH" \
  --username "$SF_USERNAME" \
  --instance-url https://login.salesforce.com

If JWT auth fails silently, check these first: private key has no passphrase, the Connected App scopes include api, the CI user profile is pre-authorized, and IP restrictions are not blocking your CI runner’s address range.

In CI, always fail fast if auth does not succeed before deploy steps run.

How should I structure metadata deploy commands to reduce failures?

Deploy specific metadata paths intentionally, validate before pushing to production, and separate destructive steps from routine deploys. Smaller, explicit deploy scopes fail less and are easier to debug.

Use the validate-then-quick-deploy pattern for production releases. Validation runs your tests against the target org without committing changes. If validation passes, quick deploy pushes the same component set without re-running tests, cutting production deployment time significantly.

# Step 1: Validate against production (runs tests, commits nothing)
sf project deploy validate \
  --source-dir force-app \
  --target-org "$SF_USERNAME" \
  --test-level RunLocalTests \
  --wait 30

# Step 2: Quick deploy the validated set (skips tests)
sf project deploy quick \
  --use-most-recent \
  --target-org "$SF_USERNAME"

Validations expire after 10 days. If you run sf project deploy quick after that window, you will get an undefinedComponentSet error and must re-validate.

For non-production environments where you want a faster feedback loop, a direct deploy is fine:

sf project deploy start \
  --source-dir force-app \
  --target-org "$SF_USERNAME" \
  --test-level RunSpecifiedTests \
  --tests MyFeatureTest \
  --wait 30

When you must run destructive changes, isolate them in their own pipeline job with explicit approval gates.

Which Apex test level should I use in each environment?

Use faster levels for lower-risk environments and stricter levels for production-bound changes. Over-testing every deploy slows teams; under-testing production deploys creates rollback pain.

Practical default matrix:

  • NoTestRun: scratch org setup tasks and metadata-only experiments.
  • RunSpecifiedTests: feature branch validation with targeted, relevant test classes.
  • RunLocalTests: integration, UAT, and production deployment paths.
  • RunAllTestsInOrg: only when policy or release criteria explicitly require it.

Keep your test strategy written in-repo so engineers do not guess per pipeline.

How do I speed up Salesforce CLI deploys without risky shortcuts?

Reduce deployment scope and avoid retesting unaffected metadata instead of lowering quality bars. The safest speed gains come from precision, not from disabling tests.

Focus on these levers:

  • Deploy only changed package directories when your repo structure supports it.
  • Use sf project deploy preview to review what will be deployed before committing to a full run.
  • Prefer RunSpecifiedTests for fast feedback in non-production stages.
  • Keep test data factories efficient so targeted tests finish quickly.
  • Cache dependencies in CI to cut setup time before CLI commands run.
  • Use .forceignore to exclude metadata that should never leave your repo. A practical starting point:
# .forceignore
package.xml

# Admin-managed metadata
**/profiles/**
**/settings/**

# IDE and OS files
.eslintrc.json
.prettierrc
.gitignore

How do I prevent CLI and plugin version drift across the team?

Pin versions where stability matters and document upgrade cadence. Unmanaged updates can break commands, output parsing, and automation scripts without warning.

Team guardrails that work:

  • Pin the CLI version in CI and publish it in README or pipeline docs.
  • Avoid unreviewed plugin installs on shared runners.
  • Schedule regular dependency updates and validate pipeline behavior in a branch first.
  • Track command changes in release notes before bumping major versions.
  • If your scripts still use sfdx commands, migrate them to the sf equivalents. Salesforce removed deprecated sfdx-style commands in November 2024, and old scripts that reference sfdx force:source:deploy or similar will fail on current CLI versions. Salesforce publishes a command migration mapping that covers every renamed command.

How do I troubleshoot failed Salesforce CLI deploys quickly?

Capture the deploy ID, fetch detailed status, and classify errors into auth, metadata validation, test failure, or platform limits. Fast classification prevents wasted retries.

Use this debug flow:

# Start deploy and capture output
sf project deploy start --source-dir force-app --target-org "$SF_USERNAME" --wait 30

# If the CLI exits without printing component errors, pull the full report
sf project deploy report --use-most-recent --target-org "$SF_USERNAME"

sf project deploy start sometimes exits without printing component-level errors. Always run sf project deploy report when you see a failure with no detail.

What if the deploy succeeded but the CLI timed out?

A common CI/CD frustration: the CLI prints The client has timed out but the deployment actually completed in Salesforce. The CLI has a client-side timeout that is separate from the server-side deploy.

When this happens:

  1. Check the deployment status in Setup > Deployment Status, or run sf project deploy report --use-most-recent once the CLI is responsive again.
  2. If the deployment succeeded, your pipeline failed for nothing. Increase the --wait value or restructure the job to poll separately.
  3. If this keeps happening, delete the .sf directory in your project root. A corrupted local state cache can cause the CLI to hang after otherwise successful operations.

If your failures are install- or environment-related, use the fixes in How to Fix Salesforce CLI NPM Installation Errors.

How do I set up a GitHub Actions pipeline for Salesforce CLI?

Define a workflow that installs the CLI, authenticates with JWT, validates against the target org, and then quick-deploys on success. This example covers the full path from push to production deploy.

Store your Connected App consumer key, username, and private key as encrypted repository secrets (SF_CLIENT_ID, SF_USERNAME, SF_JWT_KEY).

name: Salesforce Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  validate:
    name: Validate Metadata
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Install Salesforce CLI
        run: npm install @salesforce/[email protected] --global

      - name: Write JWT key file
        run: echo "${{ secrets.SF_JWT_KEY }}" > server.key

      - name: Authenticate target org
        run: |
          sf org login jwt \
            --client-id "${{ secrets.SF_CLIENT_ID }}" \
            --jwt-key-file server.key \
            --username "${{ secrets.SF_USERNAME }}" \
            --instance-url https://login.salesforce.com

      - name: Validate deployment
        run: |
          sf project deploy validate \
            --source-dir force-app \
            --target-org "${{ secrets.SF_USERNAME }}" \
            --test-level RunLocalTests \
            --wait 30

      - name: Clean up key file
        if: always()
        run: rm -f server.key

  deploy:
    name: Quick Deploy to Production
    needs: validate
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Install Salesforce CLI
        run: npm install @salesforce/[email protected] --global

      - name: Write JWT key file
        run: echo "${{ secrets.SF_JWT_KEY }}" > server.key

      - name: Authenticate target org
        run: |
          sf org login jwt \
            --client-id "${{ secrets.SF_CLIENT_ID }}" \
            --jwt-key-file server.key \
            --username "${{ secrets.SF_USERNAME }}" \
            --instance-url https://login.salesforce.com

      - name: Quick deploy validated set
        run: |
          sf project deploy quick \
            --use-most-recent \
            --target-org "${{ secrets.SF_USERNAME }}"

      - name: Clean up key file
        if: always()
        run: rm -f server.key

Pull requests run validation only. Pushes to main validate and then quick-deploy to production, using a GitHub environment with manual approval gates if you need them.

Adapt this pattern for other CI platforms (GitLab CI, Bitbucket Pipelines, Azure DevOps) by translating the YAML structure and secret references to each platform’s format.

Should I use Salesforce CLI or DevOps Center for this pipeline?

Use Salesforce CLI when you need scriptable, repeatable automation in Git-based workflows. Use DevOps Center (now Generally Available) when your team prefers guided releases with less terminal-heavy operations.

A simple decision rule:

  • Choose Salesforce CLI for high automation, custom checks, and advanced CI control.
  • Choose DevOps Center for admin-heavy teams and guided change promotion with a built-in UI.
  • Use both when needed: DevOps Center for release orchestration, CLI for custom validations and edge-case deploy logic.

If you need help before or during these CI/CD workflows, start with these: