skill/SKILL.md
Integrates the Luscii terraform-aws-ecs-service module into Terraform configurations to deploy AWS ECS Fargate services. Use when asked to "create ECS service", "deploy Fargate service", "add ECS module", "configure service connect", "set up auto-scaling for ECS", "add container definitions", or working with Luscii ECS infrastructure as code.
npx skillsauth add luscii/terraform-aws-ecs-service luscii-ecs-serviceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Integrates terraform-aws-ecs-service into Terraform configurations to deploy AWS ECS Fargate services following Luscii standards.
module "my_service" {
source = "github.com/Luscii/terraform-aws-ecs-service?ref=<version>"
context = module.label.context
name = "my-service"
ecs_cluster_name = "my-cluster"
vpc_id = var.vpc_id
subnets = var.private_subnet_ids
task_cpu = 512
task_memory = 1024
container_definitions = [
{
name = "app"
image = "123456789.dkr.ecr.eu-west-1.amazonaws.com/my-app:latest"
port_mappings = [{
containerPort = 8080
protocol = "tcp"
name = "http"
}]
log_configuration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/my-service"
"awslogs-region" = "eu-west-1"
"awslogs-stream-prefix" = "app"
}
}
}
]
egress_rules = [{
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}]
}
Source: Luscii modules are distributed via GitHub, NOT the Terraform Registry. Always use github.com/Luscii/terraform-aws-ecs-service?ref=<version>.
Copy and track:
Progress:
- [ ] Step 1: Define service basics (name, cluster, networking)
- [ ] Step 2: Configure containers
- [ ] Step 3: Choose connectivity pattern
- [ ] Step 4: Configure security groups
- [ ] Step 5: Configure volumes (if needed)
- [ ] Step 6: Set up scaling (if needed)
- [ ] Step 7: Wire up outputs
- [ ] Step 8: Validate configuration
Every service requires these inputs:
module "my_service" {
source = "github.com/Luscii/terraform-aws-ecs-service?ref=<version>"
context = module.label.context # CloudPosse label context
name = "api" # Service name
ecs_cluster_name = "production" # Existing ECS cluster
vpc_id = var.vpc_id
subnets = var.private_subnet_ids
task_cpu = 512 # CPU units
task_memory = 1024 # Memory in MiB
# ...
}
Sizing approach: Start with an educated guess on the minimum requirements for your workload, then rely on monitoring and auto-scaling to adjust. There are no fixed rules — right-size iteratively based on actual usage. Remember to account for sidecars (X-Ray: 128 CPU + 256 MiB; Envoy if using Service Connect: 256 CPU + 64 MiB).
For valid CPU/memory combinations, see references/inputs.md.
Define at least one container. Full schema at references/container-definitions.md.
Key points:
add_xray_container = false for supportive/non-core services where tracing isn't critical.task_cpu/task_memory must fit all containers + Envoy sidecar (if Service Connect is used: +256 CPU / +64 MiB, or +512 CPU / +128 MiB for high_traffic_service = true).port_mappings[].name is referenced by service_connect_configuration.port_name and validated at plan time.task_role/execution_role inputs exist for backwards compatibility but should rarely be used.Use this decision flow to pick the right pattern:
Options can be combined: B+A for external + internal ECS-to-ECS, B+C for external + full VPC discoverability. C is essentially the simplified combination of A+D.
Luscii convention: This module supports both public and internal ALB/NLB target groups, but we typically use load balancers for external access. For internal access, prefer Service Connect and Cloud Map (DNS).
A) Service Connect only (ECS-to-ECS via service mesh):
service_connect_configuration = {
namespace = "production"
port_name = "http" # Must match a port_mappings name
client_alias = {
dns_name = "api" # Other ECS services connect to api:8080
port = 8080
}
}
B) Load Balancer (public or internal access via ALB/NLB target groups):
load_balancers = [{
target_group_arn = aws_lb_target_group.app.arn
container_name = "app" # Must match a container name
container_port = 8080 # Must match a container port
}]
C) Hybrid (Service Connect + DNS for VPC-wide discoverability):
service_connect_configuration = { /* ... */ }
service_discovery_dns_namespace_ids = [
aws_service_discovery_private_dns_namespace.internal.id
]
D) Manual service registry (full control over Cloud Map):
service_registries = {
registry_arn = aws_service_discovery_service.custom.arn
container_name = "app"
container_port = 8080
}
Options C and D can be combined. Details at references/inputs.md.
The module creates a security group with self-referencing ingress. Add rules:
ingress_rules = [{
description = "Allow HTTP from ALB"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [module.alb.security_group_id]
}]
egress_rules = [{
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}]
security_group_ids = [aws_security_group.shared.id] # Additional SGs
The module supports every volume type that Fargate on Linux supports:
ephemeral (in-task scratch), Amazon EFS, and Amazon S3 Files. Default
is var.volumes = {} — skip this step if your service doesn't need
mounted storage.
Decision shortcuts (full guide at ../docs/volumes.md):
type = "ephemeral". No external resource, no IAM.type = "efs".type = "s3files" (uses Mountpoint for S3 under the hood).Quick example — S3 Files (the MESH client pattern):
volumes = {
mesh = {
type = "s3files"
s3files = {
access_point_arn = aws_s3control_access_point.mesh.arn
file_system_arn = aws_s3files_file_system.mesh.arn
kms_key_arn = aws_kms_key.mesh.arn # set when SSE-KMS
}
}
}
container_definitions = [
{
name = "mesh-client"
image = "..."
mount_points = [
{ sourceVolume = "mesh", containerPath = "/mnt/mesh" }
]
}
]
Quick example — EFS (persistent shared storage):
volumes = {
patient_data = {
type = "efs"
efs = {
file_system_id = aws_efs_file_system.patient.id
authorization_config = {
access_point_id = aws_efs_access_point.patient.id
}
}
}
}
Hardened defaults you get for free:
ENABLED — there is no opt-out, by design for the healthcare posture.true — more secure than the AWS provider's default.elasticfilesystem:Client* for EFS, s3:Get/List/Put/Delete* for S3 Files, KMS statements when kms_key_arn is set). Read/write derivation comes from mount_points[*].readOnly. Opt out per-volume with attach_iam_policy = false and attach module.<name>.volume_iam_policy_json yourself.Prerequisites the module does NOT manage (own them in your infrastructure/ Terraform):
module.<name>.security_group_idFull schema, IAM grant matrix, and validation rules at references/volumes.md. Decision guide and per-type configuration walkthrough at ../docs/volumes.md.
First enable scaling, then add policies. See references/scaling.md.
Which scaling strategy? Choose based on traffic pattern:
scaling = { min_capacity = 2, max_capacity = 10 }
scaling_target = {
cpu = {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
target_value = 70
}
}
Note: desired_count is ignored after initial creation (lifecycle ignore). Scaling manages task count.
Common output usage patterns. Full reference at references/outputs.md.
# Attach IAM policy to the task role
resource "aws_iam_role_policy_attachment" "custom" {
role = module.my_service.service_task_role_name
policy_arn = aws_iam_policy.custom.arn
}
# Reference internal URL in another service's env vars
environment = [{
name = "API_URL"
value = module.my_service.service_discovery_internal_url # "http://api:8080"
}]
# Pass label context to child modules
module "service_secrets" {
source = "github.com/Luscii/terraform-aws-service-secrets?ref=<version>"
context = module.my_service.label_context
}
# Use security group for cross-service access
resource "aws_security_group_rule" "allow" {
source_security_group_id = module.my_service.security_group_id
# ...
}
# Build on scaling target for custom policies
resource "aws_appautoscaling_policy" "custom" {
count = module.my_service.scaling_target != null ? 1 : 0
resource_id = module.my_service.scaling_target.resource_id
scalable_dimension = module.my_service.scaling_target.scalable_dimension
service_namespace = module.my_service.scaling_target.service_namespace
# ...
}
terraform fmt -recursive
terraform init
terraform validate
terraform plan
Common validation errors:
task_cpu valuetask_cpuservice_connect_configuration.port_name doesn't match any port_mappings[].nameload_balancers[].container_name doesn't match any containertype must be one of: ephemeral, efs, s3files" — invalid var.volumes[*].type valuetype..." — type/sub-block mismatch in var.volumes (e.g. type = "efs" with an s3files block, or type = "ephemeral" with anything)authorization_config.access_point_id set, root_directory must be omitted or set to "/"..." — access points root the mount themselves; drop the root_directory field or set it to /mount_points[*].sourceVolume must reference a key declared in var.volumes..." — typo in sourceVolume, or you forgot to add the volume entryadd_xray_container = false to disabletask_role/execution_role provided; policies attached either waypull_cache_prefix on containers to usevar.volumes causes the module to compute a least-privilege policy and attach it to the task role. Opt out per-volume with attach_iam_policy = false; the JSON stays available as volume_iam_policy_json for manual attachment.var.volumes[*].efs always renders transit_encryption = ENABLED. No opt-out, by design for the healthcare posture.volume.s3files_volume_configuration; older 6.x lacks the argument)development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
development
End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.