When you want to distribute modules safely inside your organization without using the public Terraform Registry, the most reliable answer is the Private Module Registry (PMR) in Terraform Cloud/Enterprise.
This article covers the big picture of the PMR, naming and SemVer operations, the publish/consume flow, permissions and visibility, testing strategy, and the key points for migration and troubleshooting. It also summarizes common Terraform exam themes (registry references vs. Git references, handling of version constraints).
The Private Module Registry ingests tags from VCS repositories as semantic versions and provides name resolution and distribution to users inside your organization. The Terraform CLI prefers source addresses resolved via the registry and can also interpret constraints in the version argument.
You can also distribute modules via raw Git URL references (git::), but the PMR is superior for discoverability, version selection, access control, and auditing. Hosting your own Registry API implementation is advanced; starting with Terraform Cloud/Enterprise is the realistic choice.
| Option | Main Benefits | Caveats / Constraints | Source Syntax |
|---|---|---|---|
| Terraform Cloud/Enterprise Private Module Registry | In-org distribution, search, visibility, version resolution, audit logs | Requires VCS integration plus naming/tag conventions; publishing is tag-driven | app.terraform.io/<org>/<name>/<provider> |
| Git source reference (git::https...) | Simple; works with any Git; usable without a registry | The version argument is not usable; pin via ref=; low discoverability | git::https://.../repo.git?ref=v1.2.3 |
| Self-hosted Registry API implementation | Stays within the corporate network; integrated control | High protocol implementation and maintenance cost; start with TFE/TFC | <host>/<org>/<name>/<provider> |
PMR-based distribution flow (conceptual diagram)
Minimal registry reference example (with version constraint)
module "vpc" {
source = "app.terraform.io/acme/vpc/aws"
version = "~> 1.2"
name = "core"
cidr = "10.0.0.0/16"
}
terraform {
required_version = ">= 1.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}The PMR recognizes modules from repository naming and tags. The naming convention is terraform-<provider>-<name> (e.g., terraform-aws-vpc). Tags must be consistent with semantic versions.
We recommend the v1.2.3 tag form (some environments accept 1.2.3, but standardize across the org). Express breaking changes as a major bump, backward-compatible additions as minor, and bug fixes as patch.
Tag and CHANGELOG example (convention sample)
Repository name: terraform-aws-vpc
Tag: v1.2.0
CHANGELOG.md excerpt:
- feat: Add flow logs support (#123)
- fix: Correct IGW dependency (#125)
- docs: Update examples for multi-AZ (#126)
Planned breaking change:
- v2.0.0 will remove variable "enable_classiclink" (due to EOL)On the publisher side, all you do is push a tag to VCS. The Terraform Cloud/Enterprise PMR detects the tag and registers it as a new version. Consumers specify a registry-format source and give a version constraint.
The CLI must authenticate to app.terraform.io. Use terraform login to create credentials (~/.terraform.d/credentials.tfrc.json), and they are picked up automatically during init.
Git reference vs. registry reference (right/wrong)
# OK: registry references can use version
module "vpc" {
source = "app.terraform.io/acme/vpc/aws"
version = ">= 1.2, < 2.0"
}
# OK: Git references pin via ref= (do not write version)
module "vpc_git" {
source = "git::https://git.example.com/terraform-aws-vpc.git?ref=v1.2.3"
}
# BAD: Git reference with a version argument (causes an error)
module "vpc_bad" {
source = "git::https://git.example.com/terraform-aws-vpc.git?ref=v1.2.3"
version = "~> 1.2" # ← not supported
}Access to the PMR depends on organization membership and team permission design. Restrict Publish and module Admin rights, while granting Read/Use broadly. In practice, the common split is "publishing is limited to the module maintainer team" and "consumption is open to all developers."
For auditing, cross-reference Terraform Cloud/Enterprise run logs, VCS tag history, and review history to see who referenced which version when. As a rule, prohibit retraction and consistently provide fixes via new patch versions.
Make the minimal layout and representative use cases runnable under examples/, and run terraform fmt -check, validate, tflint, and ideally Terratest/Kitchen-Terraform in CI. Ideally, you can verify on PRs that the examples can plan/apply.
Express compatibility explicitly in versions.tf via required_version and required_providers, and change output/input schemas in stages. Avoid breaking changes from 0 → 1 — make them only at major bumps.
versions.tf (explicit compatibility)
terraform {
required_version = ">= 1.4, < 2.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.31"
}
}
}
# Document the following in the module README:
# - Supported Terraform/Provider versions
# - Input/output compatibility policy
# - Announcements and migration steps for breaking changesWhen migrating from Git references to PMR references, first normalize existing tags to SemVer and let the PMR recognize them, then swap module.source. In existing workspaces, run terraform init -upgrade to refresh resolution information.
Typical errors include naming convention mismatch (not terraform-<provider>-<name>), missing tags, mixing version with git::, and authentication gaps (credentials.tfrc.json not configured).
Migration replacement example (Git → PMR)
# Before (Git reference)
module "vpc" {
source = "git::https://git.example.com/terraform-aws-vpc.git?ref=v1.2.0"
}
# After (PMR reference)
module "vpc" {
source = "app.terraform.io/acme/vpc/aws"
version = "~> 1.2"
}Pro
問題 1
For a module that references a Git repository directly, which behavior of Terraform is correct when you also write version = "~> 1.2" in the module block?
正解: A
The module version argument is valid only with registry references (e.g., app.terraform.io/org/name/provider). Writing it alongside a git:: reference causes an invalid-argument error. To pin a Git reference, use the ref= parameter.
How do I safely migrate from existing Git references to a Private Module Registry?
First, normalize the tags in the existing repository to SemVer (e.g., v1.2.3) and confirm the PMR recognizes them. Next, in the consuming project change source to app.terraform.io/<org>/<name>/<provider> and set a conservative version constraint (~> 1.2). After terraform login, run terraform init -upgrade to refresh resolution. For a staged migration, swap only a subset of workspaces first and observe the impact.
I published a bad version by mistake. Should I delete it?
As a rule, avoid deletion. For consumer reproducibility and auditing, ship a patch release (e.g., v1.2.1) quickly and document the issue and fix in CHANGELOG and README. Whether you can unpublish in an emergency depends on operational policy and your Terraform Cloud/Enterprise settings, so agree on the rules in advance.
Are pre-release (-rc, -beta) tags selected?
The PMR indexes SemVer-compatible tags. Terraform version resolution follows SemVer ordering, and pre-releases become candidates if they match the constraint. That said, when a stable release exists it is usually ranked higher. Recommend a policy of using only stable releases in production.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...