BLOG/ENTERPRISE AUTOMATION
THEME
ENTERPRISE AUTOMATION

A company module registry: share modules across teams

Map a company scope to one monorepo and every team imports the same versioned modules by @scope/name. The open way uses a registries: block; the managed way is OrchStep Pro's private registry.

Jul 5, 2026 OrchStep Team 8 minROLE: Platform EngineerSCALE: Enterprise
RUNNABLE DEMO
Full source for this post: blog/company-module-registry
VIEW SOURCE

You are the platform engineer, and you have the same conversation every month. A team has reinvented the deploy task, or the SBOM step, or the "wait for the rollout to go healthy" loop — slightly wrong, slightly different from the four other versions. You want one blessed implementation that every team imports and nobody forks.

A single remote module (point a source at a Git URL) solves one shared task. But platform teams do not ship one module — they ship a catalog. For that you want a scope: @yourco/deploy, @yourco/sbom, @yourco/notify, all coming from one repo, all versioned independently. That is what a registry gives you.

A company scope mapping to a monorepo of versioned modules

Map a scope to your monorepo

A custom registry is a top-level registries: block that maps a scope name to a Git repo. The map key is the scope without the leading @. Once mapped, every @yourco/<name> source resolves into that repo at modules/@<scope>/<name>/, with versions from <name>/vX.Y.Z tags.

Here is a runnable example against a real public monorepo:

orchstep.yml
name: company-module-registry
desc: "Share modules across teams: map a scope to your monorepo, then import @scope/<name>."

# Map your company scope to the monorepo that holds every shared module.
# The map key is the scope WITHOUT the leading @ (here: mycompany).
registries:
  mycompany:
    url: github.com/orchstep/monorepo-multi-modules

modules:
  - name: greet
    source: "@mycompany/module1"   # -> modules/@mycompany/module1, tag module1/vX.Y.Z
    version: "^1.0.0"

tasks:
  # `orchstep run main`
  main:
    desc: "Call a task from a shared monorepo module"
    steps:
      - name: hello
        module: greet
        task: greet
        with:
          name: "OrchStep"

The repo layout that backs this is the same one every built-in scope uses:

github.com/orchstep/monorepo-multi-modules
└── modules/
    └── @mycompany/
        ├── module1/        # tags: module1/v1.0.0, module1/v2.0.0, ...
        ├── module2/        # tags: module2/v1.0.0, ...
        └── module3/

Each module is versioned on its own tag track (module1/v1.0.0), so the deploy module can ship v3 while the notify module is still on v1. One repo, independent release cadences.

Resolve it for real

The custom @scope only means something inside a workflow that carries the registries: block — that is where the scope-to-repo mapping lives. Run module lock from the demo directory and watch @mycompany/module1 resolve to a real tag, commit, and hash:

orchstep module lock
  greet                pinned  v1.0.0  tag module1/v1.0.0  commit 3ff8a2302f1f

Lock file written: orchstep.lock

And running it pulls the module's exported task straight from the monorepo:

orchstep run main
🔒 Using locked version for 'greet': 1.0.0 (tag: module1/v1.0.0, commit: 3ff8a2302f1f)
📦 Loaded module 'greet' from '@mycompany/module1'
Workflow: company-module-registry
Task: main
  Step: hello
  $ echo 'Hello, OrchStep! (module1 v1.0.0)'
Hello, OrchStep! (module1 v1.0.0)
  [ok] hello
Result: success

Gotcha worth internalizing: the scope is only known where the registries: block is. A bare orchstep module resolve @mycompany/module1 with no registries: config in scope errors with unknown registry scope. Put the block in the workflow (or in an orchstep_config.yml next to it) and resolution just works.

What this buys the platform team

ConcernAd-hoc copiesCompany registry
One blessed implementationevery team forks it@yourco/<name> from one repo
Independent versioningall-or-nothingper-module <name>/vX.Y.Z tags
DiscoverySlack archaeologymodule search, browse the monorepo
Upgrade controledit each consumerbump the version: constraint
Reproducibility"works on my branch"module lock pins commit + hash

For your own org, swap the registry URL for your monorepo (github.com/your-org/platform-modules) and tag your modules <name>/vX.Y.Z. That is the entire open, self-hosted path — no service to run, no license, just a Git repo and tags.

When you want it managed: OrchStep Pro's private registry

The registries: approach has one constraint: module fetching uses anonymous Git, so the monorepo has to be reachable without credentials. That is fine for a public platform-modules repo, but most enterprises want their catalog closed — private by default, access-controlled, and managed without standing up and securing a shared Git repo for it.

That is what the OrchStep Pro private registry is for. It is a license-gated, local private registry with its own publish/list flow:

orchstep module publish ./deploy-kit     # publish into your private registry
orchstep module list-private             # list what's published
orchstep module export-private deploy    # export a published module

Consumers then import @yourco/deploy the same way they import any scope — the difference is the registry behind it is private and managed rather than a public Git repo.

These commands are Pro features. Without a license the binary is explicit about it:

This is an OrchStep Pro feature (the execution engine is always free).
What Pro includes: https://orchstep.dev/pro
Have a license key? Run: orchstep license activate --key=ORCH-...

To be clear about the line: the engine and every open module feature — local modules, remote Git modules, public @scope registries via registries:, the lockfile, and module validate / install / search — are free and always will be. The private registry (publish / list-private / export-private) is the managed, closed option for orgs that do not want to host the catalog themselves. Details and pricing are on the Pro page.

The honest boundary

A registry is for many shared modules. If your org shares exactly one task today, a single remote Git module (the previous post) is less ceremony — graduate to a scope when the catalog grows past two or three. And if your platform-modules repo can be public, the open registries: path needs nothing from Pro. Reach for the private registry when "closed and managed" is a hard requirement, not a nice-to-have.

Where to go next

Tired of being the human registry in your org's Slack? Map one scope to one repo and let teams import. The block above is the whole open path.

#MODULES#REGISTRY#PLATFORM#MONOREPO
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — ENTERPRISE AUTOMATION