Agent skill
opentofu-ecr-provision
Provision AWS Elastic Container Registry (ECR) repositories with GitHub OIDC integration following BETEKK standards
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/opentofu-ecr-provision
Metadata
Additional technical details for this skill
- audience
- developers
- workflow
- infrastructure-as-code
SKILL.md
OpenTofu ECR Provisioning
What I do
I guide you through provisioning AWS Elastic Container Registry (ECR) repositories following BETEKK infrastructure standards:
- ECR Repository Creation: Create ECR repositories with proper configuration and lifecycle policies
- GitHub OIDC Integration: Setup IAM roles for GitHub Actions to push/pull images
- State Management: Configure S3 backend for Terraform state management
- IAM Role Patterns: Implement standardized IAM roles with OIDC trust relationships
- Variable Naming: Follow BETEKK naming conventions for consistency
- Modular Structure: Use reusable modules for ECR and IAM resources
- Best Practices: Apply AWS Well-Architected Framework principles
When to use me
Use this skill when you need to:
- Provision a new ECR repository for Docker container images
- Setup GitHub Actions CI/CD for ECR integration
- Create standardized AWS infrastructure following BETEKK patterns
- Implement OIDC authentication for GitHub Actions
- Migrate existing ECR repositories to BETEKK standards
- Create reusable ECR infrastructure templates
Reference Implementation: ecr/betekk_probe_engine_main/
Prerequisites
- OpenTofu CLI installed: Install from https://opentofu.org/docs/intro/install/
- AWS Account: Valid AWS account with appropriate permissions
- AWS Credentials: Access keys or IAM role for authentication
- GitHub OIDC Provider: OIDC provider configured for GitHub Actions (optional, can be created as part of workflow)
- Basic OpenTofu/Terraform Knowledge: Understanding of HCL syntax and provider concepts
BETEKK Standards
Variable Naming Convention
Follow these naming patterns for consistency:
variable "environment" {
description = "Environment type (e.g., dev, prod, uat)"
type = string
default = ""
}
variable "project_prefix" {
description = "Project Prefix"
type = string
default = "betekk"
}
variable "repo_name" {
description = "ECR Repository Name Prefix"
type = string
default = "betekk-<service-name>"
}
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-southeast-1"
}
variable "state_management_bucket_name" {
description = "State Management Bucket Name"
type = string
default = "betekk-state-management-bucket"
}
variable "github_organization_old" {
description = "Github Organization Name (old) for migration support"
type = string
default = "nus-cee"
}
variable "github_organization_new" {
description = "Github Organization Name (new)"
type = string
default = "betekk"
}
Folder Structure
Standard directory structure for ECR provisioning:
<service-name>/
├── modules/
│ ├── ecr/
│ │ ├── main.tf # ECR repository resource
│ │ └── variables.tf # ECR module variables
│ └── iam-role-assume/
│ ├── main.tf # IAM assume role policy
│ └── variables.tf # IAM module variables
├── .gitignore
├── .terraform.lock.hcl
├── README.md
├── data.tf # Data sources (caller identity, etc.)
├── iam_policies.tf # IAM policy definitions
├── iam_policy_attachments.tf
├── iam_roles.tf # IAM role resources
├── main.tf # Root module with module calls
├── outputs.tf # Output values
├── providers.tf # AWS provider configuration
├── terraform.tf # Terraform configuration
└── variables.tf # Input variables
IAM Role Naming Patterns
# ECR Upload Role
resource "aws_iam_role" "ecr_<project_prefix>_github_workflows_upload_role" {
name = "${upper(var.project_prefix)}-GithubWorkflowsUploadRole"
# ...
}
# Lambda Deploy Role
resource "aws_iam_role" "betekk_lambda_github_workflows_deploy_role" {
name = "${upper(var.project_prefix)}-GithubWorkflowsLambdaDeployRole"
# ...
}
Steps
Step 1: Initialize Project
Create project directory and initialize Terraform:
# Create project directory
mkdir -p <service-name>/modules/ecr
mkdir -p <service-name>/modules/iam-role-assume
cd <service-name>
# Initialize Terraform
tofu init
Step 2: Configure Terraform
Create terraform.tf:
terraform {
required_version = "~> 1.8"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = var.state_management_bucket_name
key = "ecr/<service-name>/terraform.tfstate"
region = var.aws_region
}
}
Step 3: Configure AWS Provider
Create providers.tf:
provider "aws" {
region = var.aws_region
}
Step 4: Define Variables
Create variables.tf:
variable "environment" {
description = "Environment type (e.g., dev, prod, uat)"
type = string
default = ""
}
variable "project_prefix" {
description = "Project Prefix"
type = string
default = "betekk"
}
variable "repo_name" {
description = "ECR Repository Name Prefix"
type = string
default = "betekk-<service-name>"
}
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-southeast-1"
}
variable "state_management_bucket_name" {
description = "State Management Bucket Name"
type = string
default = "betekk-state-management-bucket"
}
variable "github_organization_old" {
description = "Github Organization Name (old)"
type = string
default = "nus-cee"
}
variable "github_organization_new" {
description = "Github Organization Name (new)"
type = string
default = "betekk"
}
Step 5: Create Data Sources
Create data.tf:
data "aws_caller_identity" "current" {}
Step 6: Create ECR Module
Create modules/ecr/main.tf:
# Create an ECR repository where Docker images will be stored
# The repository URL will be used by:
# 1. Docker push commands to upload images
# 2. Kubernetes/Helm to pull images during deployment
resource "aws_ecr_repository" "repository" {
name = var.repository_name
force_delete = false
}
# Output ECR repository URL for use in other parts of infrastructure
output "repository_url" {
description = "ECR repository URL"
value = aws_ecr_repository.repository.repository_url
}
output "repository_arn" {
description = "ECR repository ARN"
value = aws_ecr_repository.repository.arn
}
Create modules/ecr/variables.tf:
variable "repository_name" {
type = string
description = "ECR repository name"
}
Step 7: Create IAM Assume Role Module
Create modules/iam-role-assume/main.tf:
# Trust relationship is assumed to be set on role already.
# Attach policy to allow user to assume role
resource "aws_iam_policy" "assume_role_policy" {
name = "${var.project_prefix}_AssumeRolePolicy"
description = "Policy allowing assume role access to a specific role"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = "sts:AssumeRole",
Resource = var.assume_role_arn
}
]
})
}
resource "aws_iam_policy_attachment" "assume_role_policy_attachment" {
name = "${var.project_prefix}-AssumeRoleAttachment"
policy_arn = aws_iam_policy.assume_role_policy.arn
users = [var.user_arn]
}
Create modules/iam-role-assume/variables.tf:
variable "project_prefix" {
type = string
description = "Project prefix for naming"
}
variable "assume_role_arn" {
type = string
description = "ARN of role to assume"
}
variable "user_arn" {
type = string
description = "ARN of user to attach policy"
}
Step 8: Create IAM Roles
Create iam_roles.tf:
# Create IAM Role for GitHub ECR Upload
resource "aws_iam_role" "ecr_betekk_probe_engine_github_workflows_upload_role" {
name = "${upper(var.project_prefix)}-GithubWorkflowsUploadRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
"Federated" = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"token.actions.githubusercontent.com:aud" : "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub" : [
"repo:${var.github_organization_old}/<repo-name>:*",
"repo:${var.github_organization_new}/<repo-name>:*"
],
}
}
}
]
})
}
# Create IAM Role for Lambda Deployment
resource "aws_iam_role" "betekk_lambda_github_workflows_deploy_role" {
name = "${upper(var.project_prefix)}-GithubWorkflowsLambdaDeployRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
"Federated" = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"token.actions.githubusercontent.com:aud" : "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub" : [
"repo:${var.github_organization_old}/<repo-name>:*",
"repo:${var.github_organization_new}/<repo-name>:*"
]
}
}
}
]
})
}
Step 9: Create IAM Policies
Create iam_policies.tf:
# Policy for ECR Upload
resource "aws_iam_policy" "ecr_upload_policy" {
name = "${var.project_prefix}-ECRUploadPolicy"
description = "Policy allowing ECR operations"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:GetRepositoryPolicy",
"ecr:ListImages",
"ecr:DeleteRepository",
"ecr:BatchDeleteImage",
"ecr:SetRepositoryPolicy",
"ecr:DeleteRepositoryPolicy"
]
Resource = "*"
}
]
})
}
# Policy for Lambda Deployment
resource "aws_iam_policy" "lambda_deploy_policy" {
name = "${var.project_prefix}-LambdaDeployPolicy"
description = "Policy allowing Lambda operations"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"lambda:CreateFunction",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"lambda:GetFunction",
"lambda:DeleteFunction"
]
Resource = "*"
}
]
})
}
Step 10: Attach Policies
Create iam_policy_attachments.tf:
# Attach ECR policy to upload role
resource "aws_iam_role_policy_attachment" "ecr_upload_attachment" {
role = aws_iam_role.ecr_betekk_probe_engine_github_workflows_upload_role.name
policy_arn = aws_iam_policy.ecr_upload_policy.arn
}
# Attach Lambda policy to deploy role
resource "aws_iam_role_policy_attachment" "lambda_deploy_attachment" {
role = aws_iam_role.betekk_lambda_github_workflows_deploy_role.name
policy_arn = aws_iam_policy.lambda_deploy_policy.arn
}
Step 11: Create Root Module
Create main.tf:
data "aws_caller_identity" "current" {}
module "ecr_betekk_<service-name>" {
source = "./modules/ecr"
repository_name = var.repo_name
}
Step 12: Create Outputs
Create outputs.tf:
output "ecr_repository_url" {
description = "ECR repository URL"
value = module.ecr_betekk_<service-name>.repository_url
}
output "ecr_repository_arn" {
description = "ECR repository ARN"
value = module.ecr_betekk_<service-name>.repository_arn
}
output "ecr_upload_role_arn" {
description = "ARN of ECR upload role"
value = aws_iam_role.ecr_betekk_probe_engine_github_workflows_upload_role.arn
}
output "lambda_deploy_role_arn" {
description = "ARN of Lambda deploy role"
value = aws_iam_role.betekk_lambda_github_workflows_deploy_role.arn
}
Step 13: Initialize and Apply
# Initialize Terraform
tofu init
# Plan changes
tofu plan -out=tfplan
# Apply changes
tofu apply tfplan
# Show outputs
tofu output
Best Practices
Security
- Least Privilege: Grant minimal permissions in IAM roles and policies
- OIDC Authentication: Use GitHub OIDC instead of long-lived credentials
- Repository Scanning: Enable ECR image scanning for security vulnerabilities
- Encryption: Enable ECR encryption at rest (enabled by default)
- Tagging: Use consistent tags for all resources
High Availability
- Multi-Region: Deploy repositories to multiple regions if needed
- Cross-Account: Use cross-account access patterns for multi-account deployments
- Image Replication: Consider ECR replication for disaster recovery
Cost Optimization
- Lifecycle Policies: Implement ECR lifecycle policies to clean up old images
- Image Tagging: Use consistent image tagging for easy cleanup
- Monitoring: Monitor repository size and image count
State Management
- Remote State: Use S3 backend for state management
- State Locking: Enable DynamoDB table for state locking (optional)
- State Encryption: Encrypt state files
- State Versioning: Enable versioning on state bucket
Common Issues
Issue: Repository Already Exists
Symptom: Error Error: ... already exists
Solution:
# Import existing repository
tofu import aws_ecr_repository.repository <repository-name>
Issue: OIDC Provider Not Found
Symptom: Error Error: OIDC provider does not exist
Solution:
# Create OIDC provider
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
Issue: Authentication Failed
Symptom: Error Error: error configuring Terraform AWS Provider
Solution:
# Verify credentials
aws sts get-caller-identity
# Check environment variables
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
echo $AWS_DEFAULT_REGION
Issue: State Lock Error
Symptom: Error Error: Error acquiring the state lock
Solution:
# Check who has the lock
tofu state pull
# Force unlock (caution!)
tofu force-unlock <LOCK_ID>
# Or wait for other operation to complete
Examples
Complete ECR Repository Setup
# modules/ecr/main.tf
resource "aws_ecr_repository" "repository" {
name = var.repository_name
image_tag_mutability = "IMMUTABLE"
encryption_configuration {
encryption_type = "AES256"
}
image_scanning_configuration {
scan_on_push = true
}
}
ECR Lifecycle Policy
resource "aws_ecr_lifecycle_policy" "main" {
repository = aws_ecr_repository.repository.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 50 images and remove older ones"
selection = {
tagStatus = "tagged"
tagPrefixList = ["v"]
countType = "imageCountMoreThan"
countNumber = 50
}
action = {
type = "expire"
}
}
]
})
}
GitHub Actions Workflow Example
name: Build and Push to ECR
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::123456789012:role/BETEKK-GithubWorkflowsUploadRole
aws-region: ap-southeast-1
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push Docker image
run: |
docker build -t <repository-url>:latest .
docker push <repository-url>:latest
Reference Documentation
- AWS ECR Documentation: https://docs.aws.amazon.com/AmazonECR/
- Terraform AWS Provider (ECR): https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository
- GitHub OIDC: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
- OpenTofu Documentation: https://opentofu.org/docs/
- BETEKK Reference: ecr/betekk_probe_engine_main/
Related Skills
- opentofu-aws-explorer: For managing other AWS resources
- opentofu-provider-setup: For configuring AWS authentication and backends
- opentofu-provisioning-workflow: For general infrastructure provisioning patterns
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?