Spartan-ai-toolkit 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.
install
source · Clone the upstream repo
git clone https://github.com/c0x12c/ai-toolkit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/c0x12c/ai-toolkit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/toolkit/skills/terraform-service-scaffold" ~/.claude/skills/spartan-stratos-spartan-ai-toolkit-terraform-service-scaffold-955fd1 && rm -rf "$T"
manifest:
toolkit/skills/terraform-service-scaffold/SKILL.mdsource content
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
# 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
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
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
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
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
# Module entry point — locals and data sources only locals { name_prefix = "${var.project}-${var.service}-${var.env}" }
ecr.tf
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
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
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
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
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)
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)
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
bucket = "{project}-terraform-state" key = "{service}/dev/terraform.tfstate" region = "us-east-1" dynamodb_table = "{project}-terraform-locks" encrypt = true
terraform.tfvars
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
# 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, NOT
.gitignore.
# 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
# .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:
-
— backend + providerterraform/live/terraform.tf -
— all input variablesterraform/live/variables.tf -
— computed values, remote stateterraform/live/locals.tf -
— exported valuesterraform/live/outputs.tf -
— resource-per-fileterraform/modules/{service}/ -
— backend configterraform/envs/dev/state.config -
— environment valuesterraform/envs/dev/terraform.tfvars -
— sensitive values (gitignored)terraform/envs/dev/secrets.tfvars -
— CI/CD pipeline.github/workflows/terraform.yml -
includes.gitignore
secrets,*.tfvars
,.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
only, never in moduleslive/
Rules
- Use registry modules from
with{project}/terraform-modules?ref=vX.Y.Z - Version-pin all providers and modules
- Flat locals — no nested maps unless unavoidable
- Providers defined in
only, never in moduleslive/ - 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
and.gitignoresecrets.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