feat(terraform): AWS Fargate deployment module
Adds a terraform/ directory with an opinionated module that deploys shared-memory to ECS Fargate behind an ALB. The module assumes the operator already provides the VPC, RDS Postgres, ACM cert, ECR images, and OIDC clients, and creates everything else: ECS cluster + services, ALB, Service Connect namespace for app-embedder discovery, EFS-backed model cache for the embedder, Secrets Manager entries, IAM roles, CloudWatch log groups, and a one-shot migrator task definition. Includes examples/basic/ with a worked invocation and a README covering prerequisites, quick start, the post-apply migrator run, image updates, DNS setup, and a security note. Main README gains a short Mode C pointer to the terraform/ guide. Validated with `terraform fmt -check -recursive` and `terraform validate` against AWS provider 5.x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# Basic example — shared-memory on AWS Fargate
|
||||
|
||||
Minimal invocation of `../../`. Fill in your real IDs and run.
|
||||
|
||||
## Prereqs
|
||||
|
||||
Before you `terraform apply`, you need (see the [module README](../../README.md)
|
||||
for the long version):
|
||||
|
||||
- A VPC with two public + two private subnets
|
||||
- An RDS Postgres ≥ 15.5 instance with `pgvector`, `pg_trgm`, `pgcrypto`
|
||||
available (or creatable by the migrator on first run)
|
||||
- An ACM certificate in the same region as the ALB, covering `domain_name`
|
||||
- ECR repos populated with images for `apps/web` and `apps/embedder`
|
||||
- OIDC clients registered (web confidential + MCP public/PKCE)
|
||||
|
||||
## Configure
|
||||
|
||||
1. Open `main.tf` and replace the placeholder `vpc-…` / `subnet-…` /
|
||||
`arn:aws:acm:…` / image URIs with your real values.
|
||||
|
||||
2. Create `terraform.tfvars` with the sensitive inputs and chmod it:
|
||||
|
||||
```bash
|
||||
umask 077
|
||||
cat > terraform.tfvars <<EOF
|
||||
database_url = "postgres://memory:CHANGEME@my-rds-host.us-east-1.rds.amazonaws.com:5432/memory"
|
||||
oidc_client_id_web = "abc123…"
|
||||
oidc_client_secret_web = "secretvalue"
|
||||
oidc_client_id_mcp = "def456…"
|
||||
nextauth_secret = "$(openssl rand -base64 32)"
|
||||
cli_token_secret = "$(openssl rand -base64 32)"
|
||||
EOF
|
||||
chmod 600 terraform.tfvars
|
||||
```
|
||||
|
||||
## Apply
|
||||
|
||||
```bash
|
||||
terraform init
|
||||
terraform plan -out plan.out
|
||||
terraform apply plan.out
|
||||
```
|
||||
|
||||
## Post-apply
|
||||
|
||||
Open the [module README](../../README.md#post-apply) for the migrator
|
||||
`aws ecs run-task` invocation and the DNS setup.
|
||||
|
||||
The shortcut, using outputs from this directory:
|
||||
|
||||
```bash
|
||||
CLUSTER=$(terraform output -raw ecs_cluster_name)
|
||||
FAMILY=$(terraform output -raw migrator_task_definition_family)
|
||||
SG=$(terraform output -raw migrator_security_group_id)
|
||||
SUBNETS=$(terraform output -json private_subnet_ids | jq -r 'join(",")')
|
||||
|
||||
aws ecs run-task \
|
||||
--cluster "$CLUSTER" \
|
||||
--task-definition "$FAMILY" \
|
||||
--launch-type FARGATE \
|
||||
--network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SG],assignPublicIp=DISABLED}"
|
||||
```
|
||||
@@ -0,0 +1,100 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Worked example for the shared-memory Terraform module.
|
||||
#
|
||||
# This config does NOT create the VPC, RDS, ACM cert, ECR repos, or OIDC
|
||||
# clients — see ../../README.md for the prerequisite checklist. Replace the
|
||||
# placeholders below with the actual IDs from your environment.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
terraform {
|
||||
required_version = "~> 1.5"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
# Region inherits from AWS_REGION / AWS_PROFILE / shared-config. Set it
|
||||
# here only if you want to pin it explicitly.
|
||||
# region = "us-east-1"
|
||||
}
|
||||
|
||||
module "shared_memory" {
|
||||
source = "../../"
|
||||
|
||||
# ---- Identity / wiring ----
|
||||
name_prefix = "shared-memory-prod"
|
||||
vpc_id = "vpc-0123456789abcdef0"
|
||||
public_subnet_ids = ["subnet-aaa", "subnet-bbb"]
|
||||
private_subnet_ids = ["subnet-ccc", "subnet-ddd"]
|
||||
|
||||
# ---- TLS / DNS ----
|
||||
acm_certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/<uuid>"
|
||||
domain_name = "memory.example.com"
|
||||
|
||||
# ---- Images (push your own, then reference here) ----
|
||||
app_image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/shared-memory-web:v0.5.0"
|
||||
embedder_image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/shared-memory-embedder:v0.5.0"
|
||||
|
||||
# ---- Database (external RDS) ----
|
||||
# Format: postgres://USER:PASSWORD@HOST:5432/DBNAME
|
||||
# Real-world: pull from `aws_secretsmanager_secret_version` or `random_password`,
|
||||
# don't hardcode.
|
||||
database_url = var.database_url
|
||||
|
||||
# ---- OIDC ----
|
||||
oidc_issuer = "https://auth.example.com/application/o/shared-memory/"
|
||||
oidc_client_id_web = var.oidc_client_id_web
|
||||
oidc_client_secret_web = var.oidc_client_secret_web
|
||||
oidc_client_id_mcp = var.oidc_client_id_mcp
|
||||
oidc_audience = "shared-memory"
|
||||
|
||||
# ---- App-level secrets ----
|
||||
# Generate with: openssl rand -base64 32
|
||||
nextauth_secret = var.nextauth_secret
|
||||
cli_token_secret = var.cli_token_secret
|
||||
|
||||
# ---- Sizing (defaults are fine for small deployments) ----
|
||||
app_desired_count = 1
|
||||
embedder_desired_count = 1
|
||||
|
||||
tags = {
|
||||
environment = "prod"
|
||||
project = "shared-memory"
|
||||
}
|
||||
}
|
||||
|
||||
# ---- Sensitive inputs surfaced as vars so they live in terraform.tfvars
|
||||
# with 0600 perms (not in this file). See ../../README.md "Security note".
|
||||
|
||||
variable "database_url" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "oidc_client_id_web" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "oidc_client_secret_web" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "oidc_client_id_mcp" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "nextauth_secret" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "cli_token_secret" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Surface the module outputs so `terraform output` from this directory
|
||||
# gives the operator everything they need without diving into the module.
|
||||
|
||||
output "alb_dns_name" {
|
||||
description = "Point your Route53 record (alias) at this."
|
||||
value = module.shared_memory.alb_dns_name
|
||||
}
|
||||
|
||||
output "alb_zone_id" {
|
||||
description = "Used as alias.zone_id on aws_route53_record."
|
||||
value = module.shared_memory.alb_zone_id
|
||||
}
|
||||
|
||||
output "ecs_cluster_name" {
|
||||
description = "Pass to `aws ecs run-task --cluster`."
|
||||
value = module.shared_memory.ecs_cluster_name
|
||||
}
|
||||
|
||||
output "migrator_task_definition_family" {
|
||||
description = "Pass to `aws ecs run-task --task-definition`."
|
||||
value = module.shared_memory.migrator_task_definition_family
|
||||
}
|
||||
|
||||
output "migrator_security_group_id" {
|
||||
description = "Whitelist on RDS SG (inbound 5432)."
|
||||
value = module.shared_memory.migrator_security_group_id
|
||||
}
|
||||
|
||||
output "app_security_group_id" {
|
||||
description = "Whitelist on RDS SG (inbound 5432)."
|
||||
value = module.shared_memory.app_security_group_id
|
||||
}
|
||||
|
||||
output "private_subnet_ids" {
|
||||
description = "Echoed from input — handy for `aws ecs run-task --network-configuration`."
|
||||
value = module.shared_memory.private_subnet_ids_for_run_task
|
||||
}
|
||||
|
||||
output "app_log_group_name" {
|
||||
value = module.shared_memory.app_log_group_name
|
||||
}
|
||||
|
||||
output "embedder_log_group_name" {
|
||||
value = module.shared_memory.embedder_log_group_name
|
||||
}
|
||||
|
||||
output "migrator_log_group_name" {
|
||||
value = module.shared_memory.migrator_log_group_name
|
||||
}
|
||||
|
||||
output "secret_arns" {
|
||||
description = "Visibility into where the module stored its secrets."
|
||||
value = module.shared_memory.secret_arns
|
||||
}
|
||||
Reference in New Issue
Block a user