Start free
Andrew Hanna

Andrew Hanna

2GP CI/CD: How to Automate Unlocked and Managed Package Builds

2GP CI/CD: How to Automate Unlocked and Managed Package Builds

Short answer: a 2GP CI/CD pipeline creates a package version on every merge, installs that version into a fresh scratch org, runs the tests against it, and promotes only what passes. The pipeline itself is five commands. What actually breaks 2GP builds months later is not the pipeline, it is the ancestry and namespace decisions made in week one.

What does a 2GP CI/CD pipeline actually do?

Five stages, in this order:

  1. Validate the source. Static analysis and linting on the pull request, before anything is built.
  2. Build a package version. A beta version, produced from source, not from an org.
  3. Install it somewhere clean. A fresh scratch org proves the package installs, which is a different assertion from "the code deploys".
  4. Test against the installed package. Namespaced code behaves differently once packaged, so tests must run after install.
  5. Promote what passes. Promotion is the gate, and it is the only step that should be hard to trigger.

The distinction that matters: deploying metadata proves it compiles in an org you control. Installing a package version proves it works in an org you do not.

What changes between unlocked and managed 2GP?

  • Unlocked packages are for your own org estate or a customer's. No namespace is required, no ancestry, no security review. Versions are cheap and disposable.
  • Managed 2GP is for distribution: a registered namespace, intellectual property protection, ancestry, upgrade paths for subscribers, and AppExchange security review.

The pipeline shape is the same for both. The consequences of a bad promote are not: an unlocked version you regret can be replaced, a promoted managed version is public API forever.

How do you build the pipeline, step by step?

  1. Authenticate the Dev Hub non-interactively. A JWT bearer flow with a certificate stored in your CI secret store. The Dev Hub must be the org that owns the namespace, or version creation fails immediately.
  2. Create the version: sf package version create --package MyPackage --code-coverage --installation-key-bypass --wait 60. Capture the resulting 04t id as a pipeline output.
  3. Spin a scratch org: sf org create scratch --definition-file config/project-scratch-def.json --duration-days 1 --wait 10.
  4. Install and test: sf package install --package 04tXXXX --wait 20, then sf apex run test --test-level RunLocalTests --code-coverage --wait 30.
  5. Promote, on the release branch only: sf package version promote --package [email protected].

Everything before step 5 should run on every pull request. Step 5 should run on exactly one branch, behind a human approval.

How do you get ancestry and namespace right so builds do not break later?

Ancestry

Ancestry applies to managed 2GP only, and it is where automated pipelines quietly go wrong. Salesforce requires that the ancestor you specify is the highest promoted package version number for that package, and only versions promoted to managed-released state can be listed as an ancestor (Salesforce Developers). Three rules to encode in your pipeline:

  • Use the keyword. "ancestorVersion": "HIGHEST" in sfdx-project.json sets the ancestor to the highest promoted version automatically, which is exactly what CI needs. The alternatives are an explicit major.minor.patch or an ancestorId.
  • Never promote a build that only passed halfway. A promoted version becomes the next ancestor. Promote noise and your ancestry line inherits it.
  • Understand what breaking the line costs. You can break linear versioning with --skip-ancestor-check, but packages only upgrade along the ancestry line, so any subscriber sitting on an abandoned version loses their upgrade path.

Patch versions have their own rule: a patch cannot be the direct ancestor of a non-patch version.

Namespace

  • Link the namespace to the Dev Hub your CI authenticates to. This single mismatch is the most common first-run failure.
  • 2GP allows several packages in one namespace. Split by package directory in sfdx-project.json and declare inter-package dependencies explicitly, with versions.
  • Coming from 1GP? Salesforce made Package Migrations generally available in Summer '25, converting a 1GP package to 2GP with sf package convert and migrating installed subscribers across.

How do you keep a 2GP pipeline fast?

  • Pool your scratch orgs. Org creation, not package creation, is usually the slowest step. Pre-warmed orgs remove it from the critical path.
  • Scope tests by stage. Relevant tests on the pull request, RunLocalTests before promote.
  • Cache the version. If the source has not changed, do not rebuild the package.

You can assemble all of this from the Salesforce CLI plus GitHub Actions, Azure Pipelines or CumulusCI. Serpent gives you the same pipeline without the YAML: native 1GP, 2GP and managed-package workflows, cross-package dependency resolution and version management across subscriber orgs on every plan, with pre-warmed scratch org pooling on Scale. More build and release guides live in SF Guides.

FAQ

Can you automate 2GP package creation entirely?

Yes. Version creation, installation, testing and promotion are all CLI commands, so the full cycle runs unattended. Keep promotion behind a human approval anyway.

What is package ancestry in 2GP?

The declared lineage between managed package versions. It defines the upgrade path for subscribers, and packages only upgrade along that line.

Do unlocked packages need a namespace?

No. Unlocked packages can be created without one, which is why they suit internal org estates. Managed 2GP requires a registered namespace linked to your Dev Hub.

Why does my package version create fail on ancestry?

Usually because the ancestor named is not a promoted, managed-released version, or a patch version was set as the ancestor of a non-patch version.

Should CI promote every green build?

No. Promotion is irreversible in practice and sets the next ancestor, so it belongs on a release branch behind an approval, not on every merge.

Related Articles

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

Commitment free!