Skip to content

A private Terraform and OpenTofu module registry, published from GitHub tags

Once a few teams share modules, Git URLs with a ref stop scaling. A private module registry gives you versions, constraints, and a catalog. Here is when to switch and how to publish from GitHub tags.

Where most teams start: Git sources

module "network" {
  source = "git::https://github.com/acme/terraform-aws-network.git?ref=v1.2.0"
}

This works, and it has limits:

  • No version constraints. The version argument only works with registry sources, so every consumer pins an exact ref and upgrades by hand.
  • Tags can move. Someone can retag v1.2.0 to a different commit, and the next init silently downloads different code.
  • Every runner needs Git credentials for every module repository.
  • No catalog. Finding the right module means knowing which repository to look in.

What a registry adds

module "network" {
  source  = "stackship.run/acme/network/aws"
  version = "~> 1.2"
}
  • Constraints. ~> 1.2 picks up compatible fixes automatically, and 1.2.0 pins exactly.
  • Immutable versions. A published version always resolves to the same package.
  • One credential. The same login that reaches your workspaces reaches your modules.
  • A catalog with each module's README and version history.

The address has four parts: host, organization, module name, and the provider system it targets.

Versioning modules with semver

ChangeBumpExample
Bug fix, no interface changePatch1.2.0 → 1.2.1
New optional variable or outputMinor1.2.1 → 1.3.0
Renamed or removed variable, resource address changeMajor1.3.0 → 2.0.0

A resource address change is a breaking change even if every input stays the same: consumers will see a destroy and create unless you add moved blocks.

Publish from GitHub tags in StackShip

  1. Keep the module's .tf files at the root of its own repository.
  2. In Registry, select Publish module, enter a name and provider system, and choose the repository.
  3. Push a semver tag:
git tag v1.3.0
git push origin v1.3.0

Each tag becomes a version that records its repository, tag, and commit. Pushing a tag for an existing version with different contents is rejected, so a moved tag can't change what consumers get. Prerelease tags such as v2.0.0-rc.1 are only used when named exactly.

Runs authenticate to the registry automatically. On a laptop, run tofu login stackship.run; in CI, set TF_TOKEN_stackship_run. The apply reuses the modules its plan downloaded, so a new release between plan and apply can't change an approved change.

Retire versions without breaking anyone

Deprecate a version with a reason instead of deleting it. Existing pins keep working, and the module page warns people away. Restore it if you deprecated it by mistake.

See the publishing docs for limits and the API, or the platform overview for how the registry fits with workspaces and runs.

Get started

Run your first OpenTofu plan today.

Free for every organization. Sign up with email or Google.