Introduction
In the world of Infrastructure as Code (IaC), Terraform has become the standard for defining and provisioning cloud resources. A critical part of this ecosystem is the Terraform registry, a hub for discovering, sharing, and versioning modules. While HashiCorp provides a public registry, many organizations require greater control, security, and privacy for internal modules. A self-hosted registry addresses these needs by enabling private module distribution, governance enforcement, and workflow optimization.In this post, we’ll walk you through deploying your own private Terraform registry using Terrareg on Amazon ECS.
What is a Terrareg?
Terrareg is an open-source application for hosting a private Terraform module registry within your own infrastructure. It provides a secure, internal repository for your organization’s proprietary modules, complete with a web UI and API. This allows teams to easily share and consume modules while maintaining full control over versioning and access. By self-hosting, you keep sensitive code private and can accelerate deployments.
Why Use Terrareg (Benefits)?
Security & Governance:
Centralized Control over your infrastructure code, keeping sensitive modules private (behind your firewall) and maintaining a complete audit trail. It enforces strict standards like versioning, security scanning, and standardized release processes for compliance.
Consistency & Reliability:
Establishes a single source of truth to standardize infrastructure patterns (e.g., standard Kubernetes clusters) across all teams. This eliminates “copy-paste” issues and actively works to prevent configuration drift.
Visibility & Efficiency:
Provides deep visibility into module dependencies, allowing you to quickly identify projects using outdated or vulnerable module versions. The Web UI simplifies module discovery, while local hosting dramatically speeds up Terraform runs for better operational efficiency.
Architecture Overview

Our deployment architecture consists of:
- ECS Cluster: Running on EC2 instances for cost-effective compute
- Application Load Balancer: Distributing traffic and providing SSL termination
- EFS (Elastic File System): Persistent storage for registry data
- Auto Scaling Group: Ensuring high availability
- IAM Roles: Secure access management
- Security Groups: Network-level security controls
Prerequisites
- An AWS account with administrative access
- A VPC with at least 3 subnets across different availability zones
- An SSL certificate in AWS Certificate Manager (ACM) for HTTPS
Part 1: Setting Up the Foundation
Step 1: Create IAM Roles
Create ECS Task Execution Role
- Select AWS service as trusted entity type
- Attach AWS managed policy: AmazonECSTaskExecutionRolePolicy
- Name the role: ecs-terraform-registry-execution-role
- Add Inline Policy To Execution Role for Access
- Find your newly created role and and add new inline policy
- Paste the Following Json and create it with name EFSAndECRAccess :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"elasticfilesystem:DescribeFileSystems",
"elasticfilesystem:DescribeMountTargets",
"elasticfilesystem:DescribeAccessPoints",
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:ClientRootAccess",
"elasticfilesystem:ClientMount",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
Create ECS Instance Role
- Create a new Role and Select AWS service → EC2
- Attach these managed policies:
- AmazonEC2ContainerServiceforEC2Role
- Name it ecs-terraform-registry-instance-role.
Step 2: Create Security Groups
Application Load Balancer Security Group
- Name: terraform-registry-alb-sg\
- Inbound rules:
- Allow HTTPS , TCP protocol, Port 443 from anywhere.
- Outbound rules:
- Allow Custom TCP, 5000 Port to ECS SG
ECS Security Group
- Name: terraform-registry-ecs-sg
- VPC: Select your target VPC
- Inbound rules: Add the following rules:
- Allow HTTP and HTTPS , TCP protocol, Port 80, 443 from your ip.
- Allow Custom TCP 5000 Port from above ALB SG for access.
- Outbound rules: Leave default (All traffic to 0.0.0.0/0) And create
Step 3: Create EFS File System
- Name: terraform-registry-efs
- Automatic backups: Enable (recommended)
- Lifecycle management: Transition to IA after 30 days
- Performance mode: General Purpose
- Throughput mode: Bursting
- In next page on Network Configuration select your VPC
- For each availability zone:
- Choose terraform-registry-ecs-sg as a security group and then create your EFS with everything else as default.
Note the File System ID (format: fs-xxxxxxxxxx) – you’ll need this later.
Part 2: ECS Cluster Setup
Step 4: Create ECS Cluster
- Cluster name: terraform-registry-cluster
- Infrastructure: Choose Amazon EC2 instances and create it.
Step 5: Create Launch Template
- Template name: terraform-registry-launch-template
- For Application and OS Images select Browse more AMIs and then search for “ECS Optimized AMI” and select the latest Amazon Linux 2 ECS-Optimized AMI.
- Instance Type: t3.medium (adjust based on your needs)
- Key pair: Select an existing key pair or create new one for SSH access
- Network settings: On Security groups select terraform-registry-ecs-sg.
- On Advanced details:
- Select IAM instance profile we created: ecs-terraform-registry-instance-role
- User data: Add the following script and then create template:
#!/bin/bash
echo ECS_CLUSTER=terraform-registry-cluster >> /etc/ecs/ecs.config
echo ECS_ENABLE_TASK_IAM_ROLE=true >> /etc/ecs/ecs.config
echo ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST=true >> /etc/ecs/ecs.config
yum install -y amazon-efs-utils
Step 6: Create Auto Scaling Group
- Name: terraform-registry-asg
- Launch template: Select terraform-registry-launch-template
- Network: Select your VPC and select all subnets in Availability Zones and subnets.
- Set Desired capacity and Minimum capacity to 1 and Maximum capacity to 3
- And create an Auto Scaling group.
Step 7: Create Capacity Provider
- Select your ECS cluster and in Capacity providers tab create provider with Name: terraform-registry-capacity-provider
- Auto Scaling group: Select terraform-registry-asg and enable Managed scaling with 100% Target capacity.
- Disable Managed termination protection and create a provider.
- In the cluster, go to Update cluster
- Under Default capacity provider strategy select yours with Base 0 and weight 1.
Part 3: Application Deployment
Step 8: Create CloudWatch Log Group In CloudWatch
- Log group name: /ecs/terraform-registry and Retention: 7 days
Step 9: Create ECS Task Definition
- In ECS Task Definition creates a new task definition.
- Paste the following JSON (update placeholders):
{
"family": "terraform-registry",
"networkMode": "bridge",
"executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ecs-terraform-registry-execution-role",
"requiresCompatibilities": ["EC2"],
"containerDefinitions": [
{
"name": "terraform-registry",
"image": "ghcr.io/matthewjohn/terrareg:latest",
"cpu": 1600,
"memory": 1524,
"essential": true,
"environment": [
{
"name": "ADMIN_AUTHENTICATION_TOKEN",
"value": "<CHANGE_THIS_SECURE_TOKEN>"
},
{
"name": "PUBLIC_URL",
"value": "https://<your-domain.com>"
},
{
"name": "MIGRATE_DATABASE",
"value": "True"
},
{
"name": "SECRET_KEY",
"value": "<GENERATE_A_SECURE_SECRET_KEY_HERE>"
},
{
"name": "UPLOAD_API_KEYS",
"value": "<GENERATE_UPLOAD_API_KEY_HERE>"
},
{
"name": "PUBLISH_API_KEYS",
"value": "<GENERATE_PUBLISH_API_KEY_HERE>"
}
],
"portMappings": [
{
"containerPort": 5000,
"hostPort": 5000,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/terraform-registry",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"mountPoints": [
{
"sourceVolume": "efs-storage",
"containerPath": "/app",
"readOnly": false
}
]
}
],
"volumes": [
{
"name": "efs-storage",
"efsVolumeConfiguration": {
"fileSystemId": "<fs-XXXXXXXXXX>",
"rootDirectory": "/",
"transitEncryption": "ENABLED"
}
}
]
}
Step 10: Create a Target Group (Do not register the targets.)
- Target type: Instances
- Name: terraform-registry-tg
- Protocol: HTTP
- Port: 5000
- VPC: Select your VPC
- Health checks
- Protocol: HTTP
- Path: /
- Advanced health check settings: Healthy threshold: 2, Unhealthy threshold: 3, Timeout: 5 seconds, Interval: 30 seconds, Success codes: 200
Step 11: Create Application Load Balancer
- In Load Balancer pane: Choose Application Load Balancer
- Name: terraform-registry-alb
- Scheme: Internet-facing
- IP address type: IPv4
- Network mapping: Select your VPC and in Mappings select all 3 availability zones and their subnet.
- In Security groups select terraform-registry-alb-sg and remove the default security group
- Listeners HTTPS Listener (443)
- Protocol: HTTPS
- Port: 443
- Default actions: Forward to terraform-registry-tg
- Default SSL certificate: Select your ACM certificate.
- Listeners HTTP Listener (5000)
- Protocol: HTTP
- Port: 5000
- Default actions: Forward to terraform-registry-tg
Step 12: Create ECS Service
- Go to your cluster and in the Services tab create a service and select your Task Definition..
- Deployment configuration to default.
- Networking : Your VPC will be auto-selected
- Load balancing: Select Application Load Balancer type
- Load balancer: Select terraform-registry-alb
- Listener: Use existing listener (443:HTTPS)
- Target group: Use existing → terraform-registry-tg
- Service auto scaling (Optional but recommended)
- Use service auto scaling: Yes
- Set Minimum number of tasks to 1 and Max To 3
- Scaling policy: Any name,
- Metric: ECSServiceAverageCPUUtilization and Target value: 70
Step 13: Configure DNS
Go to your DNS provider and Create an CNAME record and in value add you ALB DNS name
Part 4: Verification
Check Service Status to show “RUNNING” and Check Health status and Check Target group Health ensure at least one target shows “healthy” and in the end if everything is fine then browse with your DNS.
Practical Example:
Understanding Terrareg Structure
Terrareg organizes modules in a hierarchical structure:
<registry-url>/<namespace>/<module-name>/<provider>
- Namespace: Organization/team name (e.g., “platform-team”, “networking”)
- Module Name: Specific module purpose (e.g., “vpc”, “s3-bucket”)
- Provider: Cloud provider (e.g., “aws”, “azure”)
Create Your First Module And ZIP it.
Create a standard Terraform module structure and then zip it:
my-terraform-module/
├── main.tf
├── variables.tf
├── outputs.tf
├── README.md
└── versions.tf
Upload Your Module Via
curl -X POST
https://terraform-registry.yourdomain.com/v1/terrareg/modules/namespace/platform-team/name/s3-bucket/provider/aws/version/1.0.0/upload -H "Authorization: Bearer <UPLOAD_API_KEY>" -F "file=@my-s3-module-v1.0.0.zip"
Configure Terraform to Use Your Registry
In your Terraform code use module like this:
module "example" {
source = "terraform-registry.yourdomain.com/namespace/module-name/provider"
version = "1.0.0" }
Conclusion
Deploying a self-hosted Terrareg registry on AWS ECS empowers organizations to take full control of their Terraform module ecosystem. This approach delivers a secure, private hub for internal modules, ensuring sensitive infrastructure code remains behind your firewall. By centralizing module management, you establish a single source of truth that enforces governance, standardizes patterns, and provides a complete audit trail for compliance.
Ultimately, self-hosted Terrareg boosts development efficiency through faster, local module access and enhances reliability by eliminating configuration drift. By providing deep visibility into dependencies and versions, Terrareg on ECS helps you build more robust and maintainable cloud infrastructure, turning your module library into a powerful, company-wide asset.
