Skip to content
 
 

Repository files navigation

Terraform Private Registry for AWS

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.

Architecture

Architecture

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.

Usage

Deploy the registry

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
  }
}

Configure Terraform CLI

Create a ~/.terraformrc file:

credentials "registry.example.com" {
  token = "<your-jwt-token>"
}

Consume modules

module "vpc" {
  source  = "registry.example.com/infra/vpc/aws"
  version = "2.1.0"
}

Publish modules

Use the terraform-aws-tf-registry-cli Python client to publish modules and manage the registry.

Production tips

  1. Fork this project into your enterprise git server and add a remote tracking this repository
  2. Deploy using the example/registry as a starting point — see the step-by-step guide for deploy, configure, release, and pull workflow
  3. Publish a test module, verify DynamoDB entries, run terraform init
  4. Integrate the CLI client into your CI/CD pipeline
  5. See additional notes for operational guidance

References

Requirements

Name Version
terraform >= 1.7.0
archive ~> 2.8
aws ~> 5.90
external ~> 2.3
random ~> 3.5

Providers

No providers.

Modules

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

Resources

No resources.

Inputs

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)
[
"EDGE"
]
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({
host = string
acm_certificate_arn = string
})
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({
block_public_acls = bool,
ignore_public_acls = bool,
block_public_policy = bool,
restrict_public_buckets = bool
})
{
"block_public_acls": true,
"block_public_policy": true,
"ignore_public_acls": true,
"restrict_public_buckets": true
}
no
secret_key_name Optional AWS Secret name to store JWT secret string null no
storage n/a
object({
dynamodb = object({
name = optional(string, null)
billing_mode = optional(string, "PAY_PER_REQUEST")
read = optional(number, 1)
write = optional(number, 1)
})
bucket = object({
name = optional(string, null)
})
})
{
"bucket": {
"name": null
},
"dynamodb": {
"billing_mode": "PAY_PER_REQUEST",
"name": null,
"read": 1,
"write": 1
}
}
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

Outputs

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.

About

Terraform module for creating a simple private serverless Terraform registry in AWS

Topics

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages