terraform-service-scaffold
Generate complete service-level Terraform infrastructure with modules, environments, and CI/CD. Use when adding Terraform to a new service or bootstrapping infrastructure from scratch.
Works with
---
name: terraform-service-scaffold
description: Generate complete service-level Terraform infrastructure with modules, environments, and CI/CD. Use when adding Terraform to a new service or bootstrapping infrastructure from scratch.
license: MIT
---
# Terraform Service Scaffold
Generates a complete service-level Terraform structure with live orchestration, reusable modules, environment configs, and CI/CD workflow.
## When to Use
- Adding Terraform to a new service repository
- Bootstrapping service infrastructure from scratch
- Setting up a new microservice with standard cloud resources
## Process
### 1. Gather Requirements
Ask the user:
- **Service name** (e.g., `{service}`)
- **Container host**: ECS or EKS
- **Resources needed**: RDS, Redis, S3, SQS, or combination
- **Environments**: which environments to scaffold (default: dev)
### 2. Read Infra Remote State
```hcl
# Check existing shared infrastructure outputs
data "terraform_remote_state" "infra" {
backend = "s3"
config = {
bucket = "{project}-terraform-state"
key = "infra/terraform.tfstate"
region = var.region
}
}
```
### 3. Create Live Orchestration
Location: `terraform/live/`
#### terraform.tf
```hcl
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = var.project
Service = var.service
Environment = var.env
ManagedBy = "terraform"
}
}
}
```
#### variables.tf
```hcl
variable "project" {
description = "Project identifier"
type = string
}
variable "service" {
description = "Service name"
type = string
}
variable "env" {
description = "Environment (dev, staging, prod)"
type = string
}
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
```
#### locals.tf
```hcl
locals {
name_prefix = "${var.project}-${var.service}-${var.env}"
vpc_id = data.terraform_remote_state.infra.outputs.vpc_id
private_subnet_ids = data.terraform_remote_state.infra.outputs.private_subnet_ids
public_subnet_ids = data.terraform_remote_state.infra.outputs.public_subnet_ids
common_tags = {
Project = var.project
Service = var.service
Environment = var.env
}
}
```
#### outputs.tf
```hcl
output "ecr_repository_url" {
description = "ECR repository URL for {service}"
value = module.{service}.ecr_repository_url
}
output "service_endpoint" {
description = "Service endpoint URL"
value = module.{service}.service_endpoint
}
```
### 4. Create Service Module
Location: `terraform/modules/{service}/`
One resource per file:
#### main.tf
```hcl
# Module entry point — locals and data sources only
locals {
name_prefix = "${var.project}-${var.service}-${var.env}"
}
```
#### ecr.tf
```hcl
module "ecr_backend" {
source = "git::https://github.com/{project}/terraform-modules.git//ecr?ref=v1.0.0"
name = "${local.name_prefix}-backend"
image_tag_mutability = "IMMUTABLE"
lifecycle_policy_rules = [
{
description = "Keep last 20 images"
count_number = 20
tag_status = "any"
count_type = "imageCountMoreThan"
}
]
}
module "ecr_worker" {
source = "git::https://github.com/{project}/terraform-modules.git//ecr?ref=v1.0.0"
name = "${local.name_prefix}-worker"
image_tag_mutability = "IMMUTABLE"
lifecycle_policy_rules = [
{
description = "Keep last 20 images"
count_number = 20
tag_status = "any"
count_type = "imageCountMoreThan"
}
]
}
```
#### rds.tf
```hcl
module "rds" {
source = "git::https://github.com/{project}/terraform-modules.git//rds?ref=v1.0.0"
name = "${local.name_prefix}-db"
engine = "postgres"
engine_version = "15.4"
instance_class = var.rds_instance_class
allocated_storage = var.rds_allocated_storage
db_name = replace(var.service, "-", "_")
master_username = var.db_username
master_password = var.db_password
subnet_ids = var.private_subnet_ids
vpc_id = var.vpc_id
deletion_protection = var.env == "prod" ? true : false
backup_retention_period = var.env == "prod" ? 30 : 7
multi_az = var.env == "prod" ? true : false
}
```
#### redis.tf
```hcl
module "redis" {
source = "git::https://github.com/{project}/terraform-modules.git//elasticache?ref=v1.0.0"
name = "${local.name_prefix}-cache"
engine = "redis"
node_type = var.redis_node_type
num_cache_nodes = 1
subnet_ids = var.private_subnet_ids
vpc_id = var.vpc_id
transit_encryption = true
at_rest_encryption = true
auth_token = var.redis_auth_token
}
```
#### s3.tf
```hcl
module "s3" {
source = "git::https://github.com/{project}/terraform-modules.git//s3?ref=v1.0.0"
bucket_name = "${local.name_prefix}-assets"
versioning = true
server_side_encryption = {
sse_algorithm = "aws:kms"
}
block_public_access = {
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
}
```
#### sqs.tf
```hcl
module "sqs" {
source = "git::https://github.com/{project}/terraform-modules.git//sqs?ref=v1.0.0"
name = "${local.name_prefix}-queue"
visibility_timeout_seconds = 300
message_retention_seconds = 1209600
receive_wait_time_seconds = 20
dead_letter_queue = {
enabled = true
max_receive_count = 3
retention_seconds = 1209600
}
}
```
#### ecs.tf (if container host is ECS)
```hcl
module "ecs_service" {
source = "git::https://github.com/{project}/terraform-modules.git//ecs-service?ref=v1.0.0"
name = local.name_prefix
cluster_id = var.ecs_cluster_id
task_definition = module.ecs_task.arn
desired_count = var.desired_count
subnet_ids = var.private_subnet_ids
security_groups = [aws_security_group.service.id]
target_group_arn = aws_lb_target_group.service.arn
container_name = "${local.name_prefix}-backend"
container_port = var.container_port
}
module "ecs_task" {
source = "git::https://github.com/{project}/terraform-modules.git//ecs-task?ref=v1.0.0"
family = local.name_prefix
cpu = var.task_cpu
memory = var.task_memory
execution_role = var.ecs_execution_role_arn
task_role = var.ecs_task_role_arn
image = "${module.ecr_backend.repository_url}:${var.image_tag}"
container_port = var.container_port
environment = var.container_environment
secrets = var.container_secrets
}
resource "aws_lb_target_group" "service" {
name = local.name_prefix
port = var.container_port
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
path = "/health"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 30
}
}
```
#### eks.tf (if container host is EKS)
```hcl
resource "kubernetes_namespace" "service" {
metadata {
name = var.service
labels = {
project = var.project
env = var.env
}
}
}
resource "kubernetes_service_account" "service" {
metadata {
name = var.service
namespace = kubernetes_namespace.service.metadata[0].name
annotations = {
"eks.amazonaws.com/role-arn" = module.irsa.role_arn
}
}
}
module "irsa" {
source = "git::https://github.com/{project}/terraform-modules.git//irsa?ref=v1.0.0"
name = "${local.name_prefix}-irsa"
oidc_provider_arn = var.oidc_provider_arn
namespace = var.service
service_account = var.service
policy_arns = var.irsa_policy_arns
}
resource "kubernetes_secret" "service" {
metadata {
name = "${var.service}-secrets"
namespace = kubernetes_namespace.service.metadata[0].name
}
data = var.k8s_secrets
}
resource "kubernetes_config_map" "service" {
metadata {
name = "${var.service}-config"
namespace = kubernetes_namespace.service.metadata[0].name
}
data = var.k8s_config
}
```
### 5. Create Environment Config
Location: `terraform/envs/dev/`
#### state.config
```hcl
bucket = "{project}-terraform-state"
key = "{service}/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "{project}-terraform-locks"
encrypt = true
```
#### terraform.tfvars
```hcl
project = "{project}"
service = "{service}"
env = "dev"
region = "us-east-1"
# RDS
rds_instance_class = "db.t3.micro"
rds_allocated_storage = 20
db_username = "{service}_admin"
# Redis
redis_node_type = "cache.t3.micro"
# Container
desired_count = 1
task_cpu = 256
task_memory = 512
container_port = 8080
image_tag = "latest"
```
#### secrets.tfvars
```hcl
# Encrypted via git-secret-protector — committed to git, decrypted at CI/CD time
db_password = ""
redis_auth_token = ""
```
#### git-secret-protector Setup
Secrets are encrypted in git using [git-secret-protector](https://github.com/nicovince/git-secret-protector), NOT `.gitignore`.
```bash
# 1. Install git-secret-protector
pip install git-secret-protector
# 2. Initialize per-environment filters
git-secret-protector init --filter secrets-dev
git-secret-protector init --filter secrets-prod
# 3. Add .gitattributes rules for auto-encrypt on commit
cat >> .gitattributes <<'EOF'
terraform/live/envs/dev/secrets.tfvars filter=secrets-dev
terraform/live/envs/prod/secrets.tfvars filter=secrets-prod
EOF
# 4. Store encryption keys securely (CI/CD secrets, NOT in repo)
# GitHub Actions: store as repository secret GIT_SECRET_PROTECTOR_KEY_DEV / _PROD
```
The secrets file IS committed to git (encrypted). On checkout, the smudge filter decrypts it
if the key is available. In CI/CD, decrypt before terraform plan/apply.
### 6. Generate CI/CD Workflow
```yaml
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths: ['terraform/**']
push:
branches: [main]
paths: ['terraform/**']
env:
TF_VERSION: '1.11'
ENVIRONMENT: dev
jobs:
plan:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Decrypt secrets
run: |
pip install git-secret-protector
git-secret-protector reveal --filter secrets-${{ env.ENVIRONMENT }}
env:
GIT_SECRET_PROTECTOR_KEY: ${{ secrets.GIT_SECRET_PROTECTOR_KEY_DEV }}
- name: Init
run: |
cd terraform/live
terraform init -backend-config=envs/${{ env.ENVIRONMENT }}/state.config
- name: Plan
run: |
cd terraform/live
terraform plan \
-var-file=envs/${{ env.ENVIRONMENT }}/terraform.tfvars \
-var-file=envs/${{ env.ENVIRONMENT }}/secrets.tfvars \
-no-color
apply:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Decrypt secrets
run: |
pip install git-secret-protector
git-secret-protector reveal --filter secrets-${{ env.ENVIRONMENT }}
env:
GIT_SECRET_PROTECTOR_KEY: ${{ secrets.GIT_SECRET_PROTECTOR_KEY_DEV }}
- name: Init
run: |
cd terraform/live
terraform init -backend-config=envs/${{ env.ENVIRONMENT }}/state.config
- name: Apply
run: |
cd terraform/live
terraform apply \
-var-file=envs/${{ env.ENVIRONMENT }}/terraform.tfvars \
-var-file=envs/${{ env.ENVIRONMENT }}/secrets.tfvars \
-auto-approve
```
### 7. Output Checklist
After generating all files, produce this checklist:
- [ ] `terraform/live/terraform.tf` — backend + provider
- [ ] `terraform/live/variables.tf` — all input variables
- [ ] `terraform/live/locals.tf` — computed values, remote state
- [ ] `terraform/live/outputs.tf` — exported values
- [ ] `terraform/modules/{service}/` — resource-per-file
- [ ] `terraform/envs/dev/state.config` — backend config
- [ ] `terraform/envs/dev/terraform.tfvars` — environment values
- [ ] `terraform/envs/dev/secrets.tfvars` — sensitive values (gitignored)
- [ ] `.github/workflows/terraform.yml` — CI/CD pipeline
- [ ] `.gitignore` includes `*.tfvars` secrets, `.terraform/`, `*.tfstate*`
## Interaction Style
- Asks service name, container host, and resources before generating
- Generates all files in one pass — no partial output
- Uses registry modules with version pinning, never inline resources
- Keeps providers in `live/` only, never in modules
## Rules
- Use registry modules from `{project}/terraform-modules` with `?ref=vX.Y.Z`
- Version-pin all providers and modules
- Flat locals — no nested maps unless unavoidable
- Providers defined in `live/` only, never in modules
- One resource per file in modules
- Remote state for cross-stack references
- Sensitive variables marked with `sensitive = true`
- All resources tagged with project, service, environment, ManagedBy
- State stored in S3 with DynamoDB locking
- Secrets never committed — use `.gitignore` and `secrets.tfvars`
## Output
Produces a complete directory tree:
```
terraform/
live/
terraform.tf
variables.tf
locals.tf
outputs.tf
modules/{service}/
main.tf
ecr.tf
rds.tf
redis.tf
s3.tf
sqs.tf
ecs.tf (or eks.tf)
variables.tf
outputs.tf
envs/dev/
state.config
terraform.tfvars
secrets.tfvars
.github/workflows/terraform.yml
```More Deployment & CI/CD skills
azure-enterprise-infra-planner
microsoft/azure-skills
Architect and provision enterprise Azure infrastructure from workload descriptions. For cloud architects and platform engineers planning networking, identity, security, compliance, and multi-resource topologies with WAF alignment. Generates Bicep or Terraform directly (no azd). WHEN: 'plan Azure infrastructure', 'architect Azure landing zone', 'design hub-spoke network', 'plan multi-region DR topology', 'set up VNets firewalls and private endpoints', 'subscription-scope Bicep deployment', 'Azure Backup for VM workloads'. PREFER azure-prepare FOR app-centric workflows.
azure-kubernetes-app-deploy
microsoft/azure-skills
Use when deploying an existing web application or API to an already-running Azure Kubernetes Service cluster. Detects the framework, generates a Dockerfile and Kubernetes manifests, validates against AKS Deployment Safeguards, and deploys with verification. WHEN: deploy app to AKS, deploy to existing AKS cluster, containerize app for Kubernetes, generate K8s manifests for Azure, set up CI/CD for AKS, my AKS deployment is failing safeguard checks, I have a Django/Express/Spring Boot app to run on AKS. DO NOT USE FOR: creating or provisioning an AKS cluster (use azure-kubernetes), assessing migration to AKS Automatic (use azure-kubernetes-automatic-readiness), or deploying to non-AKS targets like Web Apps, Container Apps, or Functions.
finetuning
microsoft/azure-skills
Fine-tune models on Microsoft Foundry using SFT (supervised), DPO (preference), or RFT (reinforcement with graders). Covers dataset preparation, training job submission, deployment, and evaluation. USE FOR: fine-tune, SFT, DPO, RFT, training data, grader, distillation, fine-tuned model, training job, large file upload, calibrate grader, deploy fine-tuned model, evaluate fine-tuned model. DO NOT USE FOR: general model deployment without fine-tuning (use deploy-model), agent creation (use agents), prompt optimization without training (use prompt-optimizer).

