This Terraform module deploys a private registry for Terraform on AWS, allowing you to publish and consume your own modules independently of the public registry at registry.terraform.io.
module "awesomeapp" {
source = "registry.example.com/myorg/awesomeapp/aws"
version = "1.0.0"
}Battle-tested in production for 2+ years. Costs less than $10/month.
This is a fork from terraform-aws-tf-registry by Martin Atkins.
The registry implements Terraform's module registry protocol using:
- API Gateway — serves the registry HTTP API with JWT-based authorization
- DynamoDB — stores the module index (namespace/name/provider → versions)
- S3 — stores module artifacts (zip files)
- Lambda (authorizer) — validates JWT bearer tokens
- Lambda (download) — generates S3 presigned URLs for module downloads
- Secrets Manager — stores the JWT signing key
Key design choices:
- Presigned S3 URLs allow cross-account/cross-platform access without sharing AWS credentials
- The
s3::prefix in module sources triggers Terraform's AWS-style authentication (docs) - Multi-account sharing is simplified — only JWT tokens need to be distributed
For implementation details, see the registry-service module documentation.
See the full example at example/registry.
module "registry" {
source = "geronimo-iia/tf-registry/aws"
version = "~> 1.3"
name_prefix = "registry"
storage = {
dynamodb = { name = "my-registry-tfe" }
bucket = { name = "my-registry-tfe" }
}
friendly_hostname = {
host = "registry.example.com"
acm_certificate_arn = aws_acm_certificate.cert.arn
}
tags = {
Product = "Registry"
ProductComponent = "terraform"
}
}
# Point DNS to the registry
resource "aws_route53_record" "registry" {
zone_id = data.aws_route53_zone.main.zone_id
name = "registry.example.com."
type = "A"
alias {
name = module.registry.dns_alias.hostname
zone_id = module.registry.dns_alias.route53_zone_id
evaluate_target_health = true
}
}Create a ~/.terraformrc file:
credentials "registry.example.com" {
token = "<your-jwt-token>"
}module "vpc" {
source = "registry.example.com/infra/vpc/aws"
version = "2.1.0"
}Use the terraform-aws-tf-registry-cli Python client to publish modules and manage the registry.
- Fork this project into your enterprise git server and add a remote tracking this repository
- Deploy using the example/registry as a starting point — see the step-by-step guide for deploy, configure, release, and pull workflow
- Publish a test module, verify DynamoDB entries, run
terraform init - Integrate the CLI client into your CI/CD pipeline
- See additional notes for operational guidance
| Name | Version |
|---|---|
| terraform | >= 1.7.0 |
| archive | ~> 2.8 |
| aws | ~> 5.90 |
| external | ~> 2.3 |
| random | ~> 3.5 |
No providers.
| Name | Source | Version |
|---|---|---|
| authorizer | ./modules/registry-authorizer | n/a |
| download | ./modules/registry-download | n/a |
| jwt | ./modules/registry-jwt | n/a |
| registry | ./modules/registry-service | n/a |
| store | ./modules/registry-store | n/a |
No resources.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| api_access_policy | If using a Private API requires you to have an access policy configured and accepts a string, but must be valid json. Defaults to Null | string |
null |
no |
| api_type | Sets API type if you want a private API without a custom domain name, defaults to EDGE for public access | list(string) |
[ |
no |
| domain_security_policy | Sets the TLS version to desired state, defaults to 1.2 | string |
"TLS_1_2" |
no |
| dynamodb_enable_point_in_time_recovery | Enable DynamoDB point in time recovery | bool |
true |
no |
| friendly_hostname | Configures a "friendly hostname" that will be used to reference objects in this registry. If this is set, the given hostname and certificate will be registered against the created API. Can be left unset if the service discovery information will be separately published at the friendly hostname, using the "services" output value. | object({ |
null |
no |
| kms_key_id | Optional custom kms key id (default aws/secretsmanager) | string |
null |
no |
| name_prefix | A name to use as the prefix for the created API Gateway REST API, DynamoDB tables, etc | string |
"terraform-registry" |
no |
| s3_public_access | Bucket Public Access Block | object({ |
{ |
no |
| secret_key_name | Optional AWS Secret name to store JWT secret | string |
null |
no |
| storage | n/a | object({ |
{ |
no |
| tags | Resource tags | map(string) |
{} |
no |
| vpc_endpoint_ids | Sets the VPC endpoint ID for a private API, defaults to null | list(string) |
null |
no |
| Name | Description |
|---|---|
| bucket_arn | Bucket arn |
| bucket_name | Bucket name |
| dns_alias | If the friendly_hostname input variable is set, this exports the hostname and Route53 zone id that should be used to point the friendly hostname at the registry API. If not using Route53 for DNS, you can alternatively create a regular CNAME record to the returned hostname. If friendly hostname is not enabled then this output is always null. |
| dynamodb_table_arn | Dynamodb table arn |
| dynamodb_table_name | Dynamodb table name |
| registry_secret_key_name | JWT secret key name in aws secret manager |
| rest_api_id | The id of the API Gateway REST API managed by this module. |
| rest_api_stage_name | The id of the API Gateway deployment stage managed by this module. |
| services | A service discovery configuration map for the deployed services. A JSON-serialized version of this should be published at /.well-known/terraform.json on an HTTPS server running at the friendly hostname for this registry. |
