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
versionargument only works with registry sources, so every consumer pins an exact ref and upgrades by hand. - Tags can move. Someone can retag
v1.2.0to a different commit, and the nextinitsilently 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.2picks up compatible fixes automatically, and1.2.0pins 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
| Change | Bump | Example |
|---|---|---|
| Bug fix, no interface change | Patch | 1.2.0 → 1.2.1 |
| New optional variable or output | Minor | 1.2.1 → 1.3.0 |
| Renamed or removed variable, resource address change | Major | 1.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
- Keep the module's
.tffiles at the root of its own repository. - In Registry, select Publish module, enter a name and provider system, and choose the repository.
- Push a semver tag:
git tag v1.3.0
git push origin v1.3.0Each 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.