This guide walks you through setting up the multi-org DevOps blueprint from scratch — forking the repository, creating orgs, configuring CI/CD, and verifying your first deployment.
- Prerequisites
- Fork the Repository
- Create Scratch Orgs
- Authenticate and Store Auth URLs
- Configure GitHub Environments
- Run Initial Deployment
- Verify with Smoke Tests
- Optional: Configure Slack Notifications
Install the following tools before proceeding.
The Salesforce CLI (sf) is required for all org interactions.
# macOS (Homebrew)
brew install sf
# npm (all platforms)
npm install -g @salesforce/cli
# Verify installation
sf version
# Expected: @salesforce/cli/2.x.x ...Ensure you are on version 2.x or later. The legacy sfdx command is deprecated.
# macOS
brew install git
# Verify
git --version
# Expected: git version 2.x.xThe GitHub CLI (gh) simplifies repository and secret management.
# macOS
brew install gh
# Authenticate
gh auth login
# Verify
gh auth statusYou need a Salesforce org with Dev Hub enabled to create scratch orgs.
- Log in to your Dev Hub org.
- Go to Setup > Dev Hub and ensure it is enabled.
- Authorize the CLI:
sf org login web --set-default-dev-hub --alias devhub# Fork and clone in one step
gh repo fork christopherramm/TDX-2026-Salesforce-DevOps-Multi-Org --clone --remote
cd TDX-2026-Salesforce-DevOps-Multi-Org
# Verify the remote setup
git remote -v
# origin https://github.com/YOUR-USERNAME/TDX-2026-Salesforce-DevOps-Multi-Org.git (fetch)
# upstream https://github.com/christopherramm/TDX-2026-Salesforce-DevOps-Multi-Org.git (fetch)If you prefer to create the fork via the GitHub UI, clone your fork afterward:
git clone https://github.com/YOUR-USERNAME/TDX-2026-Salesforce-DevOps-Multi-Org.git
cd TDX-2026-Salesforce-DevOps-Multi-OrgWe create three scratch orgs to simulate the EU, US, and APAC environments. Each org uses the same scratch org definition but will receive different metadata.
# Create the EU scratch org
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias eu-scratch \
--duration-days 7 \
--set-default \
--target-dev-hub devhub
# Create the US scratch org
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias us-scratch \
--duration-days 7 \
--target-dev-hub devhub
# Create the APAC scratch org
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias apac-scratch \
--duration-days 7 \
--target-dev-hub devhub
# Verify all three orgs exist
sf org list --allIf you are using sandboxes instead of scratch orgs, authenticate to each one:
# Authenticate to each sandbox
sf org login web --alias eu-sandbox --instance-url https://test.salesforce.com
sf org login web --alias us-sandbox --instance-url https://test.salesforce.com
sf org login web --alias apac-sandbox --instance-url https://test.salesforce.comGitHub Actions needs SFDX Auth URLs to authenticate with each org. An auth URL is a portable credential string that the CLI can use without interactive login.
Run this for each org:
# EU org
sf org display --target-org eu-scratch --verbose --json | \
python3 -c "import sys,json; print(json.load(sys.stdin)['result']['sfdxAuthUrl'])"
# US org
sf org display --target-org us-scratch --verbose --json | \
python3 -c "import sys,json; print(json.load(sys.stdin)['result']['sfdxAuthUrl'])"
# APAC org
sf org display --target-org apac-scratch --verbose --json | \
python3 -c "import sys,json; print(json.load(sys.stdin)['result']['sfdxAuthUrl'])"Each command outputs a string like force://PlatformCLI::...@login.salesforce.com. Copy these values.
Create the following GitHub repository secrets. The secret names must match exactly — they are referenced in the workflow files and config/org-registry.json.
# Store each auth URL as a GitHub secret
gh secret set SFDX_AUTH_URL_EU --body "force://PlatformCLI::YOUR_EU_AUTH_URL"
gh secret set SFDX_AUTH_URL_US --body "force://PlatformCLI::YOUR_US_AUTH_URL"
gh secret set SFDX_AUTH_URL_APAC --body "force://PlatformCLI::YOUR_APAC_AUTH_URL"For production orgs, use separate secrets:
gh secret set SFDX_AUTH_URL_PROD_EU --body "force://PlatformCLI::YOUR_PROD_EU_AUTH_URL"
gh secret set SFDX_AUTH_URL_PROD_US --body "force://PlatformCLI::YOUR_PROD_US_AUTH_URL"
gh secret set SFDX_AUTH_URL_PROD_APAC --body "force://PlatformCLI::YOUR_PROD_APAC_AUTH_URL"| Secret Name | Purpose | Used By |
|---|---|---|
SFDX_AUTH_URL_EU |
EU sandbox/scratch org authentication | deploy-shared, deploy-org-deltas, validate-pr |
SFDX_AUTH_URL_US |
US sandbox/scratch org authentication | deploy-shared, deploy-org-deltas, validate-pr |
SFDX_AUTH_URL_APAC |
APAC sandbox/scratch org authentication | deploy-shared, deploy-org-deltas, validate-pr |
SFDX_AUTH_URL_PROD_EU |
EU production org authentication | deploy-shared (production), deploy-org-deltas (production) |
SFDX_AUTH_URL_PROD_US |
US production org authentication | deploy-shared (production), deploy-org-deltas (production) |
SFDX_AUTH_URL_PROD_APAC |
APAC production org authentication | deploy-shared (production), deploy-org-deltas (production) |
SLACK_WEBHOOK_URL |
(Optional) Slack notifications | All workflows |
GitHub Environments allow you to add approval gates and environment-specific secrets for production deployments.
Navigate to your repository on GitHub: Settings > Environments, then create:
| Environment Name | Purpose | Required Reviewers | Wait Timer |
|---|---|---|---|
eu-sandbox |
EU sandbox deployments | None | None |
us-sandbox |
US sandbox deployments | None | None |
apac-sandbox |
APAC sandbox deployments | None | None |
production-eu |
EU production deployments | 1-2 reviewers | 5 minutes |
production-us |
US production deployments | 1-2 reviewers | 5 minutes |
production-apac |
APAC production deployments | 1-2 reviewers | 5 minutes |
Alternatively, use the GitHub CLI:
# Create sandbox environments (no protection rules)
gh api repos/{owner}/{repo}/environments/eu-sandbox --method PUT --input /dev/null
gh api repos/{owner}/{repo}/environments/us-sandbox --method PUT --input /dev/null
gh api repos/{owner}/{repo}/environments/apac-sandbox --method PUT --input /dev/null
# Create production environments
# Note: Protection rules (required reviewers) must be configured via the GitHub UI
# or via the API with appropriate payloads.
gh api repos/{owner}/{repo}/environments/production-eu --method PUT --input /dev/null
gh api repos/{owner}/{repo}/environments/production-us --method PUT --input /dev/null
gh api repos/{owner}/{repo}/environments/production-apac --method PUT --input /dev/nullTo add required reviewers via the API (replace REVIEWER_ID with the GitHub user ID):
gh api repos/{owner}/{repo}/environments/production-eu \
--method PUT \
--input - <<EOF
{
"reviewers": [
{"type": "User", "id": REVIEWER_ID}
],
"wait_timer": 5
}
EOFEnable branch protection on main to enforce PR-based workflows:
gh api repos/{owner}/{repo}/branches/main/protection --method PUT --input - <<'EOF'
{
"required_status_checks": {
"strict": true,
"contexts": ["validate"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true
},
"restrictions": null
}
EOFDeploy all shared packages and org-specific metadata to each org for the first time. Shared packages must be deployed in dependency order: core → integration → logic.
# Step 1: Shared packages (in order)
sf project deploy start --source-dir packages/core --target-org eu-scratch --wait 10
sf project deploy start --source-dir packages/integration --target-org eu-scratch --wait 10
sf project deploy start --source-dir packages/logic --target-org eu-scratch --wait 10
# Step 2: EU-specific metadata
sf project deploy start --source-dir orgs/eu --target-org eu-scratch --wait 10sf project deploy start --source-dir packages/core --target-org us-scratch --wait 10
sf project deploy start --source-dir packages/integration --target-org us-scratch --wait 10
sf project deploy start --source-dir packages/logic --target-org us-scratch --wait 10
sf project deploy start --source-dir orgs/us --target-org us-scratch --wait 10sf project deploy start --source-dir packages/core --target-org apac-scratch --wait 10
sf project deploy start --source-dir packages/integration --target-org apac-scratch --wait 10
sf project deploy start --source-dir packages/logic --target-org apac-scratch --wait 10
sf project deploy start --source-dir orgs/apac --target-org apac-scratch --wait 10Check that all components deployed successfully:
# Quick check — list recently deployed components
sf project deploy report --target-org eu-scratch
sf project deploy report --target-org us-scratch
sf project deploy report --target-org apac-scratchRun the smoke test script against each org to confirm that the deployment is functional.
# Make the script executable (if not already)
chmod +x scripts/smoke-test.sh
# Run smoke tests for each org
./scripts/smoke-test.sh eu-scratch
./scripts/smoke-test.sh us-scratch
./scripts/smoke-test.sh apac-scratchTo run specific test classes only:
# Run only specific test classes against the EU org
./scripts/smoke-test.sh eu-scratch "GlobalIdServiceTest,EURoutingServiceTest"Expected output for a passing run:
[smoke-test] Smoke Test Run
[smoke-test] Target org : eu-scratch
[smoke-test] Test classes : all local tests
[smoke-test] Timeout : 10 minutes
[smoke-test] Output format : human
========================================================================
[smoke-test] Verifying connectivity to eu-scratch...
[smoke-test] Org is reachable.
[smoke-test] Running all local tests...
Outcome : Passed
Total : 12
Passing : 12
Failing : 0
Skipped : 0
Duration : 8423 ms
========================================================================
[smoke-test] RESULT: ALL SMOKE TESTS PASSED
========================================================================
Receive deployment notifications in a Slack channel.
- Go to Slack API: Incoming Webhooks.
- Create a new app (or use an existing one) and enable Incoming Webhooks.
- Add a webhook to the channel where you want deployment notifications.
- Copy the webhook URL.
gh secret set SLACK_WEBHOOK_URL --body "https://hooks.slack.com/services/T.../B.../..."The GitHub Actions workflows include optional Slack notification steps that trigger on deployment success or failure. These steps check for the presence of the SLACK_WEBHOOK_URL secret and skip gracefully if it is not configured.
Example notification payload (sent automatically by the workflows):
{
"text": "Deployment to EU completed successfully",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Deployment Complete*\nOrg: EU Sandbox\nPackages: core, integration, logic\nStatus: Success\nCommit: `abc1234`"
}
}
]
}If sf org display --verbose does not show an sfdxAuthUrl, your org may not support it. Try re-authenticating:
sf org login web --alias eu-scratch
sf org display --target-org eu-scratch --verbose --jsonEnsure your Dev Hub is properly configured and has available scratch org capacity:
sf org list limits --target-org devhub --json | \
python3 -c "import sys,json; [print(f\"{l['name']}: {l['remaining']}/{l['max']}\") for l in json.load(sys.stdin)['result'] if 'Scratch' in l['name']]"Shared packages must be deployed in order. If integration fails because it references a core class, ensure core was deployed first:
# Always deploy in this order
sf project deploy start --source-dir packages/core --target-org YOUR_ORG --wait 10
sf project deploy start --source-dir packages/integration --target-org YOUR_ORG --wait 10
sf project deploy start --source-dir packages/logic --target-org YOUR_ORG --wait 10
sf project deploy start --source-dir orgs/YOUR_REGION --target-org YOUR_ORG --wait 10Verify that:
- The workflow files are on the
mainbranch (GitHub only reads workflows from the default branch). - The
pathsfilters in the workflow match the directories you changed. - GitHub Actions is enabled for your repository (Settings > Actions > General).
Once the initial setup is complete:
- Create a feature branch and make a small change to test the PR validation workflow.
- Merge to main and verify that the deployment workflows trigger correctly.
- Review the architecture docs — see ARCHITECTURE.md and DECISION-LOG.md.
- Add your own packages — extend the
packages/directory with additional shared packages as your org grows. - Add more orgs — update
config/org-registry.json,config/deployment-order.json, thedetect-changes.shscript, and the workflow files to support additional orgs.