DOC_INDEX
THEME
DOCS/Modules/Using Modules

Using Modules

Import, install, version, and call modules — with runnable examples

This is the consumer guide: every way to reference a module, the CLI commands, version constraints, and how cached modules resolve. Every example here uses a real, public module repo, so you can run them as-is.

Sourcing a module

1. Built-in registry scope

@orchstep, @community, and @ai are built in — they resolve to the public OrchStep registry repo with no configuration. Modules live at modules/@<scope>/<name>/ and are tagged <name>/vX.Y.Z.

modules:
  - name: clone
    source: "@community/github_action_git-checkout"
    version: "^1.0.0"

tasks:
  main:
    steps:
      - name: checkout
        module: clone
        task: checkout
        with:
          repository: "https://github.com/org/repo.git"
          path: repo
          ref: main

2. Full Git URL (single-module repo)

Point source: at any Git repository. The whole repo is the module — its orchstep-module.yml / orchstep.yml live at the repo root, and versions come from vX.Y.Z tags.

modules:
  - name: greeter
    source: "github.com/orchstep/test-module-single"   # tags: v1.0.0, v1.1.0, v2.0.0
    version: "^1.0.0"                                   # resolves to v1.1.0

tasks:
  main:
    steps:
      - name: hi
        module: greeter
        task: hello
        with:
          name: "OrchStep"

3. Custom registry (your own monorepo)

To host many modules in one repo under your own scope, map the scope to the repo with a registries: block. Modules live at modules/@<scope>/<name>/ and are tagged <name>/vX.Y.Z — exactly like the built-in scopes.

registries:
  mycompany:
    url: github.com/orchstep/monorepo-multi-modules

modules:
  - name: greet
    source: "@mycompany/module1"
    version: "^1.0.0"
  - name: calc
    source: "@mycompany/module2"
    version: "^1.0.0"
  - name: fmt
    source: "@mycompany/module3"
    version: "^1.0.0"

tasks:
  main:
    steps:
      - { name: a, module: greet, task: greet, with: { name: "OrchStep" } }
      - { name: b, module: calc,  task: add,   with: { a: "2", b: "3" } }
      - { name: c, module: fmt,   task: upper, with: { text: "hello" } }

The registries: map key is the scope without the @ (mycompany), referenced as @mycompany/.... You can override a tag template with tag: "{module}/v{version}" (the default).

Calling a module's tasks

Import modules in the modules: block (giving each a local name: alias), then call their exported tasks from a step with module: + task::

modules:
  - name: greeter
    source: "github.com/orchstep/test-module-single"
    version: "^1.0.0"

tasks:
  main:
    steps:
      - name: run_it
        module: greeter          # the alias from modules:
        task: hello              # an exported task
        with:                    # task parameters
          name: "World"
  • Pass parameters with with:.
  • Provide module config (its config.schema) with a config: block on the import.
  • Use the dedicated module: + task: keys — a dotted task: greeter.hello is not valid.

Module context modes: library vs configured instance

A module can be called two ways, and you pick per call:

  1. As a library (the default) — the caller injects the data (with: / env: / import config:); the module is reusable logic with fallback defaults:.
  2. As a configured instance — the module is self-contained and ships its OWN config: environments/<profile> vars, a dotenv:, and an env: block. You select a profile and the module loads its own config.

How the engine resolves a module call: instance_profile? then isolate? select one of four modes — Inherited, Configured, Sealed, Pinned

Two call-step knobs select the mode:

  • instance_profile: <name> — load the module's own environments/<name> (plus its dotenv: and env:), resolved relative to the module. Template-resolved; use "@caller" to follow the caller's active environment name.
  • isolate: true — clean-room: exclude the caller's context and overrides (with: / env: / import config:). The OS environment is always inherited.
isolate: falseisolate: true
no instance_profileInherited — caller drives (default)Sealed — module's own baseline, isolated
instance_profile: pConfigured — profile p, caller can overridePinned — profile p, clean-room
tasks:
  deploy_all:
    steps:
      # Library call — caller drives.
      - { module: notifier, task: send, with: { message: "starting" } }

      # Configured instance — the module loads its OWN prod profile + dotenv + env.
      - { module: billing, task: migrate, instance_profile: prod }

      # Pinned — exact prod instance, ignores anything from the caller.
      - { module: billing, task: migrate, instance_profile: prod, isolate: true }

      # Fan out: one --env drives each module's OWN same-named profile.
      - { module: billing, task: migrate, instance_profile: "@caller" }

What loads, low → high precedence:

LayerInheritedConfiguredSealedPinned
OS environmentyesyesyesyes
module defaults:yesyesyesyes
module environments/<profile>yesyes
module dotenv: / env:yesyesyes
caller --env varsyes
caller overrides (with: / env: / config:)yesyes

Everything a configured instance loads is scoped: its profile vars and dotenv unwind when the module returns, so two instances of the same module in one run (a monorepo fan-out) stay independent, and nothing leaks into the caller's later steps. A named instance_profile the module does not declare fails fast.

Inherited is unchanged. A plain module: + task: call (no instance_profile, no isolate) behaves exactly as before — the module's own environments//dotenv:/env: are loaded only when you opt into instance mode.

CLI commands

module install — fetch into the cache

Accepts a built-in scope, a custom scope, or a full Git URL, with an optional @version (or a second argument):

orchstep module install @community/github_action_git-checkout
orchstep module install github.com/orchstep/test-module-single@v1.0.0
orchstep module install github.com/orchstep/test-module-single ^1.0.0

Modules are fetched into ~/.orchstep/cache/modules/. (Custom @scope sources need their registries: config available, e.g. in orchstep_config.yml.)

run --module — run a task without a workflow file

# List exported tasks, their params, and a copy-paste command
orchstep run --module @community/github_action_git-checkout --list-tasks

# Run a task directly
orchstep run --module github.com/orchstep/test-module-single hello --var name=OrchStep

Great for one-off tasks and CI bootstrap (e.g. replacing actions/checkout@v4).

module resolve / module info — inspect versions

orchstep module resolve @orchstep/demo-validate-json          # which version a constraint selects
orchstep module info github.com/orchstep/test-module-single   # git URL, scope, sub-path, available versions

module search — find registry modules

orchstep module search git-checkout
# @community/github_action_git-checkout  v1.0.0  Clone Git repos ...  [community]

module validate — check a local module directory

orchstep module validate ./my-module/            # structural checks; supply-chain issues are warnings
orchstep module validate ./my-module/ --strict   # supply-chain issues become errors (the @ai submission gate)

module lock — pin exact versions {#lockfile}

orchstep module lock     # resolve every imported module and write orchstep.lock

orchstep.lock records the exact version, tag, and source for each module so teammates and CI get identical resolutions. Commit it.

# orchstep.lock (auto-generated)
modules:
    greeter:
        source: github.com/orchstep/test-module-single
        version: 1.1.0
        tag: v1.1.0
        commit: ""

module cache — manage the local cache

orchstep module cache path     # print the cache directory
orchstep module cache clean    # remove all cached modules

There is no orchstep module list command — use module cache path to find cached modules, or module info <source> to inspect a specific one.

Version constraints

ConstraintMatchesExample
1.2.3exactonly 1.2.3
^1.2.0>=1.2.0 <2.0.01.2.0, 1.9.9
~1.2.0>=1.2.0 <1.3.01.2.0, 1.2.99
>=1.2.0minimum1.2.0, 2.0.0
*highest availablelatest tag

The @version suffix on module install accepts a v-prefixed exact version (@v1.2.0) or any constraint (@^1.0.0).

Where modules are cached

~/.orchstep/cache/modules/<repo-path>/@<scope>/<name>/v<version>/   # scope/monorepo
~/.orchstep/cache/modules/<repo-path>/v<version>/                   # single-module repo

The scope is part of the cache key, so @orchstep/x and @community/x never collide even though both resolve to the same registry repo.

Next steps