Open-source alternative to GitHub Packages

GitHub Packages can be replaced by self-hosted registries in Gitea or Forgejo, Pulp for multi-format packages, or Harbor for container images. The right mix depends on which package types you publish and how your CI pulls them.

On this page

Which open-source alternative to GitHub Packages fits the package types you publish?

No single open-source product covers every GitHub Packages registry type equally, so the choice is made per package type. Gitea and Forgejo include built-in package registries that cover npm, Maven and container images among their supported types, Pulp is a multi-format repository manager with plugins for npm, Maven, Gem and others, and Harbor is the OCI-focused option for container images. Your team will usually end up with one of these for each format it publishes, or with a deliberate choice to consolidate on the one that covers most of them.

Start by listing every package type you publish to GitHub Packages today, along with the consumers of each one: CI jobs, internal developers, production deployments and external users. The maturity of each option differs by format. In Pulp, for example, the pulp_npm and pulp_maven plugins are still 0.x releases, and the pulp_rust plugin is a tech preview whose APIs may change without notice, so Cargo is not a format to plan around yet. Confirm the current state of each plugin you need in its repository before you commit, and check whether NuGet and RubyGems are covered by your chosen platform at all, since they are not in the Pulp plugin list supplied for this page.

The rest of this section is covered below: how each registry type maps to a candidate, what Maven publishing requires in Pulp, how billing and token rules change, and what migration involves for existing packages and consumers.

How each GitHub Packages registry maps to open-source options

This table maps each GitHub Packages registry to the self-hosted option that covers it, with coverage notes for Gitea or Forgejo, Pulp and Harbor. Treat each row as a starting point for testing, since package-type support depends on the version you deploy. The main takeaway is that npm, Maven and container images have the clearest paths, while Gradle, NuGet and RubyGems need confirmation before you commit.

GitHub Packages registryOpen-source option and maturityWhat to confirm before deciding
npmGitea and Forgejo list npm among their package types. Pulp's pulp_npm is 0.10.2, a 0.x release.Scoped package naming, publish tokens and access rules in the exact version you plan to run.
RubyGemsGitea and Forgejo package types must be checked for RubyGems. Pulp's pulp_gem is 0.8.0, a 0.x release.Whether Gitea or Forgejo serves gem push and gem install in your version, and whether a Pulp gem plugin meets your publish needs.
MavenGitea and Forgejo list Maven among their package types. Pulp's pulp_maven is 0.35.0, a 0.x release, and can act as a pull-through cache.Pulp's Maven deploy path does not use authentication and does not separate SNAPSHOT from release repositories, so confirm publish controls.
GradleGradle publishes through Maven repository layouts, so coverage depends on the Maven support in each option.Gitea or Forgejo support for Gradle publishing and the Gradle metadata they serve, tested with your own build.
NuGetNo NuGet plugin appears in Pulp's list of content plugins. Gitea and Forgejo NuGet support must be confirmed.Whether Gitea or Forgejo serves the NuGet protocol in your version, and what replaces it if it does not.
Docker and OCI containersHarbor is the dedicated option for OCI images. Pulp's pulp_container 2.30.0 syncs and hosts images, but its pull-through cache is a tech preview.Gitea or Forgejo container registry limits, Harbor's fit for your image workflow and whether your team needs the Pulp cache mode.

When Gitea or Forgejo, Pulp or Harbor fits better

GitHub Packages bundles several registry types into one service tied to your repositories. The self-hosted options split that job: a forge with built-in registries, a multi-format repository manager, or a container registry. Each card below describes where one option fits, what it gives up, and what to verify before you commit.

Gitea built-in package registries

Gitea is a good fit when packages should sit next to the repositories, issues and Gitea Actions that already publish them, and when one small team wants a single server to run. Gitea supports more than 20 registry types, including npm, PyPI, Maven and container images. The trade-off is that its registries are a feature of the forge, so the depth of each format depends on Gitea's own implementation. Confirm that each format your build uses, including the exact Maven or npm client behavior, works in a test project before migration. Commercial support for Gitea comes from CommitGo, Inc.

Forgejo built-in package registries

Forgejo fits teams that want the same forge-hosted model as Gitea but under GPL-3.0-or-later from v9.0 onward, with governance independent of any single company. Its registries are built into the forge, so publishing and access follow repository permissions. Forgejo Actions is not compatible with GitHub Actions, so any workflow that publishes packages must be rewritten, not just moved. Check which registry formats your pipeline needs against Forgejo's current documentation, because format coverage is the part most likely to differ from GitHub Packages.

Pulp for multi-format repositories

Pulp fits organizations that publish several package types, such as npm, Maven, Ruby gems, Python and RPM, and need one repository manager with a single REST API under /pulp/api/v3/. It is licensed GPLv2+ and describes itself as completely free and open source. Maturity varies by plugin: pulp_rpm and pulp_python are past version 1.0, while pulp_maven, pulp_npm and pulp_gem are still 0.x. The pulp_rust plugin is a tech preview without authentication support, so do not rely on it for private registries. Pulp's publish flow for Maven also lacks authentication, so restrict network access or test the workflow first.

Harbor for container images

Harbor fits teams whose main GitHub Packages use is container images and who want an OCI registry as a separate service from their forge. Pulp's container plugin covers Docker and OCI syncing and hosting, but its pull-through cache mode is a tech preview, so Harbor may be the simpler choice for a production image cache. Harbor's format coverage, license terms and current release line were not part of the supplied research, so confirm them directly with the Harbor project before you plan around them. Check image signing, vulnerability scanning and replication requirements against your own policy.

What GitHub Packages billing and token rules change for your team

Moving registries changes which downloads are free and which are billed. GitHub's billing documentation lists storage and data transfer quotas by plan: 500 MB of storage and 1 GB of data transfer per month on Free, 2 GB and 10 GB on Team, and 50 GB and 100 GB on Enterprise Cloud. Storage is shared with GitHub Actions artifacts, so a team that keeps large build outputs in Actions also consumes its package quota. Downloads are free for public packages, for data transferred in, and for GitHub Actions jobs that authenticate with GITHUB_TOKEN. Self-hosted runners that pull packages with a personal access token are billed. Check which of your pipelines use a personal access token today, because those are the costs that move first.

The GITHUB_TOKEN pattern disappears when packages move. Your own registry needs its own credentials, so plan a service account or deploy token per pipeline, stored in your CI secret store, with read and publish rights scoped to the repositories it needs. Gitea and Forgejo registries and Pulp or Harbor each use their own authentication model, so confirm the token types, expiry settings and per-format client configuration (for example, the .npmrc or Maven settings.xml entries your developers already use) in a test project before you change any build. Remove the old tokens from workflows once the new ones work, and rotate any that were shared between teams.

On your own registry the bill becomes an operating cost you own. You pay for servers, block or object storage that grows with every published version, backups of the registry database and artifacts, upgrades and the staff time to run them, and any support contract you choose. Storage and backup volume are driven by retention rules, the number of versions kept per package and how often pipelines republish. Ask any vendor or partner for a written scope that states who patches the registry, how backups are tested and what happens during an outage, and compare that scope with the per-plan quotas you are leaving. Confirm the current GitHub terms yourself, since GitHub has promised at least one month's notice before changing free container storage and bandwidth.

Registry-by-registry paths for npm, Maven, NuGet, RubyGems and containers

For npm packages, the candidate is Pulp with the pulp_npm plugin. It mirrors packages from upstream registries such as registry.npmjs.org, offers pull-through caching and accepts packages published with npm publish or yarn publish. Most teams keep their package scopes and change the registry endpoint, so the visible change is mainly in .npmrc and in the credentials used by CI.

  • Plugin maturity: pulp_npm is at 0.10.2, a 0.x release. Pin the version in your deployment and expect configuration details to change between releases.
  • Publish and install commands: npm publish and npm install keep their syntax, but the registry value in .npmrc moves from the GitHub endpoint to your Pulp content URL.
  • Upstream mirroring: confirm that public dependencies resolve through the mirror, including scoped packages, and that lockfile integrity hashes still match.
  • Publish credentials: GITHUB_TOKEN publishing from Actions does not carry over. Create a dedicated publish credential for each pipeline and store it in your secrets manager.

Before cutover, test publish, install, and access control for each scope, and confirm how deprecation and removal of versions are handled in your setup.

Keeping the rest of your GitHub setup while packages move

Moving packages off GitHub touches more than the registry. CI pipelines, source hosting and identity all depend on the same GitHub tokens and accounts, so each needs a decision before the first package is republished. The cards below cover what each piece costs to keep, what breaks if it is left alone, and what to confirm with the platform vendor.

CI pipelines using GITHUB_TOKEN

Workflows that publish with the built-in GITHUB_TOKEN will fail once the registry is no longer GitHub Packages, and the token itself only exists inside GitHub Actions runs. Rewrite each publish step to authenticate against the new registry with a scoped credential stored as a CI secret. Gitea Actions is designed to be compatible with GitHub Actions, so many workflow files may run with few changes, but confirm each action you use is available on the Gitea instance. Test a publish from a branch before switching the default branch.

Actions compatibility differs by forge

Gitea Actions is designed to be compatible with GitHub Actions, while Forgejo Actions is designed to be familiar to GitHub Actions users but is not compatible with them. If your publish workflows depend on marketplace actions or GitHub-specific contexts, a Forgejo move means rewriting those steps and running separate runners. Inventory every uses: line across publish workflows before choosing the forge. Ask the vendor which runner types and labels are supported for your build images.

Repository hosting can stay on GitHub

Source hosting does not have to move in the same change as packages. Keeping repositories on GitHub while the registry runs elsewhere means publish jobs call an external registry from GitHub-hosted or self-hosted runners, which adds a network path and a credential that must be kept current. Plan this as an interim state with an end date. Confirm with your platform team whether runners can reach the self-hosted registry over the network you use and whether egress from GitHub-hosted runners is allowed by your policies.

Identity and registry access tokens

GitHub Packages access is tied to personal access tokens, fine-grained tokens and organization roles, so each consumer currently holds a credential that maps to a GitHub identity. The new registry issues its own tokens, and every developer, service account and runner needs a new credential with the same read or write scope. Map each existing role to a registry permission, rotate tokens on a schedule, and remove access for leavers. Confirm with the vendor how tokens are created, whether they can be scoped per repository or per package, and whether they can be linked to your single sign-on provider.

Billing and quota changes at cutover

GitHub Packages bills storage and data transfer on plan allowances, and self-hosted runners that download with a personal access token are billed. Once packages leave, those charges stop, but the storage and bandwidth of the new registry become your own cost. Before cutover, list which consumers pull packages through GitHub, including self-hosted runners, so none fail when the allowance no longer applies. Confirm the current GitHub billing terms on the GitHub Packages billing page before setting a cutover date.

How to move published packages and tokens off GitHub Packages

This sequence moves each published package and version to a self-hosted registry with verified checksums, switches consumers and CI to the new source, and retires GitHub publishing only after both registries have run side by side.

  1. Inventory every package, version and consumer

    Before anything moves, list each package by registry type (npm, Maven, NuGet, RubyGems or container image), its published versions, its owning repository and every consumer that installs it, including build pipelines, deployment jobs and developer machines. Record which versions are still referenced and which are deprecated or unused. Ask the GitHub account owner to confirm the export path available for your account and plan, because packages cannot be assumed to export in bulk. The inventory is done when every package has a row listing its versions, consumers and owner, and a named person has signed off on it.

  2. Stand up the target registry in a test environment

    Choose the target by format. Gitea or Forgejo's built-in package registries can cover npm, Maven, NuGet, RubyGems and container images for a single forge, while Pulp suits multi-format repositories and Harbor suits OCI images. Deploy the chosen platform in a non-production environment with the same authentication model you plan to use in production, and confirm in its documentation that each registry type your inventory lists is supported. The step is complete when a test package can be published and installed from a clean machine using the new endpoint.

  3. Republish or mirror versions and verify checksums

    Move each version either by republishing the original artifact or by mirroring it where the target supports upstream sync. Keep the original artifact files rather than rebuilding them from source, so the bytes stay the same. Compare the checksum of every migrated file against the value recorded from GitHub, and keep the comparison log with the inventory. Treat any mismatch as a blocker and investigate before continuing. The step is complete when every version in the inventory has a matching checksum record.

  4. Handle deprecated versions and tokens explicitly

    Decide what happens to versions marked deprecated and to any version that will not be migrated, and document that decision for each one. Create new access tokens on the target registry scoped to the least privilege each consumer needs, and do not copy GitHub personal access tokens into the new system. Revoke GitHub tokens only after the cut-over step. The step is complete when a written list shows every deprecated version and every token with its owner, scope and replacement.

  5. Update consumer configuration and CI secrets

    Point each package manager configuration at the new registry URL, such as the registry setting in a project's npm or Maven configuration, and replace the GitHub token references in CI with the new secrets. Check that build runners can reach the new endpoint from their network. Pilot the change on one low-risk repository first and confirm that installs and publishes succeed with the new credentials. The step is complete when a pilot build installs and publishes through the new registry without any GitHub credential in use.

  6. Run both registries in parallel for a defined window

    Keep GitHub Packages available for reads while consumers gradually move to the new registry. Set the window length in writing before it starts, and agree who monitors failed installs and failed publishes during it. Watch for consumers still pulling from GitHub, using the dependency and build logs as evidence. Confirm that GitHub billing and storage figures are stable during the window, since the storage and transfer allowances in GitHub's billing rules apply to packages still hosted there. The step is complete when the monitoring log shows no remaining consumers on GitHub endpoints.

  7. Cut over and retire GitHub publishing

    Switch publishing to the new registry, disable publish workflows that still target GitHub, and revoke the GitHub tokens listed in the earlier step. Archive the inventory, checksum log and deprecation decisions with the change record so auditors can trace each artifact. Keep read access to the old packages for the period your records require, then remove them. The step is complete when publishing runs only on the new registry, the GitHub tokens show as revoked, and the change record is filed.

What buyers ask before moving packages off GitHub

What does moving packages off GitHub Packages cost, and which parts drive it?

The cost splits into one-time work and recurring running costs. One-time work covers mapping each registry type, migrating or re-publishing packages, updating CI and developer tooling, and testing. Recurring costs cover the servers or VMs, object storage for package blobs, backups, upgrades, and the staff time or support contract that keeps the registry running. GitHub Packages bills storage and data transfer that are shared with Actions artifacts, with plan-based allowances listed in GitHub's billing documentation, so your current invoice and usage report are the baseline to compare against. Ask any quote to itemize each component separately and to state which parts are fixed and which scale with package volume and download traffic.

How long does a package cutover take, and what sets that time?

The duration depends on the number of registry types, the number and size of packages, how many CI pipelines and developer machines point at the old registry, and how many consumers rely on pinned versions. Teams usually run both registries in parallel during cutover so that existing builds keep resolving while new publishes move. Pulp can mirror or pull through upstream registries, which can shorten the overlap for some formats. Ask for a written plan with phases, a list of consumers, and a rollback step for each registry type before agreeing on a schedule.

What does the internal team need to provide during the move?

The internal team has to supply a complete inventory of what is published, who publishes it, and which pipelines use it. Engineers need to confirm package naming, scopes, version policies and how SNAPSHOT or pre-release builds are used. A platform owner should approve hosting, storage sizing, backup targets and identity integration. Security staff should review who may publish and who may read each repository. Without these inputs, the scope cannot be fixed in writing.

Can our own engineers migrate packages without an outside partner?

Yes, for a small set of registries with a limited number of consumers. Gitea and Forgejo include package registries for common formats, and Harbor or Pulp can host OCI images and multi-format packages, so a capable platform team can run a pilot. The harder parts are the operational ones: upgrades, backups, access control, monitoring and the cutover of CI pipelines. Teams that lack these skills often choose a fixed-scope engagement for those parts. DeployOpen plans, implements, hardens, backs up and documents self-hosted platforms in your environment under written scope, and a quote follows from that scope.

Which package registry types are not yet mature enough for production?

Maturity varies by format and by plugin. In Pulp, pulp_rpm, pulp_python, pulp_container and pulp_deb are past version 1.0, while pulp_maven, pulp_npm, pulp_gem, pulp_hugging_face and pulp_ansible are still 0.x releases, so their APIs may still change. The pulp_rust plugin is labeled a tech preview whose APIs may change without notice, and it does not yet support authentication, authorization or full registry syncing. The pulp_maven deploy feature does not use authentication and does not separate SNAPSHOT from release repositories. Pulp's container pull-through cache is also a tech preview. Confirm the current status of each plugin before you commit a format to production.

How do tokens, permissions and CI pipelines change after the move?

Each registry has its own authentication model, so GitHub tokens and GITHUB_TOKEN-based workflows will not carry over unchanged. You need to create service accounts or deploy tokens in the new registry, store them as CI secrets, and update the publish and install settings in npm, Maven, NuGet, RubyGems and container tooling. Gitea and Forgejo Actions can run workflows, but Forgejo Actions is not compatible with GitHub Actions, and Gitea Actions is designed to be compatible, so confirm each workflow's behavior in a test run. Plan token rotation and revocation of the old GitHub tokens as part of the cutover.

What does a fixed-scope DeployOpen engagement cover for packages?

A DeployOpen engagement is agreed in writing before work starts. The written scope typically names the registry types in use, the target platform, the hosting environment, the integration with your identity provider, the CI changes, the cutover and rollback steps, backup and upgrade procedures, and the documentation handed over. DeployOpen does not publish a price list, so a quote follows from that written scope. Ongoing support, upgrades and continuous testing are available as retainers. Anything outside the agreed scope, such as formats you have not listed, should be scoped separately so that cost and timing stay clear.

How DeployOpen scopes and delivers a package registry replacement

DeployOpen plans, implements, integrates, hardens, backs up and documents self-hosted open-source platforms inside your own cloud accounts, data centers or air-gapped networks. For a GitHub Packages replacement, we start by inventorying which package formats your teams publish and consume, then map each one to a registry that runs in your environment.

How we scope

  • We inventory every npm, Maven, NuGet, RubyGems and container package your teams publish, with consumers and CI jobs that depend on each one.
  • Engagements are fixed-scope, and the written scope names the registries, storage backend, identity integration and cutover deliverables before work begins.
  • We confirm with you which plugin maturity levels and tech-preview features your formats depend on, so no one is surprised after go-live.

How we build

  • We deploy the chosen registries, such as Gitea or Forgejo package registries, Pulp or Harbor, in your infrastructure with the storage and database you specify.
  • We configure authentication tokens, access rules and proxy or mirror settings to match your current GitHub token and billing model.
  • We test publish and install paths for each format, including CI pipelines, and run security testing against the registry endpoints before cutover.

How we hand over

  • You receive architecture, runbooks, backup and restore procedures, and configuration records for every registry we deploy.
  • We document the client-side changes developers must make, such as registry URLs and token setup for npm, Maven and NuGet clients.
  • We prepare evidence of configuration and controls for your compliance assessors, while independent auditors remain responsible for any audit opinion.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer after handover, covering registry versions and plugin updates.
  • We can back up and upgrade the registry platform on a schedule agreed in the written scope, with changes documented for your team.
  • Ask for a written quote from a defined scope that lists components, responsibilities and what recurs after go-live, since we do not publish a price list.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.