diff --git a/templates/awshp-k8s-rag-with-claude-code/README.md b/templates/awshp-k8s-rag-with-claude-code/README.md new file mode 100644 index 0000000..7053cd8 --- /dev/null +++ b/templates/awshp-k8s-rag-with-claude-code/README.md @@ -0,0 +1,50 @@ +# AWS RAG Application Prototyping with Coder CDE + +A Kubernetes-based Coder template that provides a complete development environment for AWS RAG (Retrieval-Augmented Generation) application prototyping with Claude Code integration. + +## Architecture + +This template creates: +- **Kubernetes workspace** with configurable CPU/memory resources +- **Aurora PostgreSQL Serverless v2** cluster with pgvector extension for vector storage +- **Claude Code integration** with AWS Bedrock for AI-assisted development +- **Pre-configured development environment** with AWS CLI, CDK, and Python tooling + +## Key Components + +### Infrastructure (`main.tf`) +- Kubernetes deployment with Coder agent +- Configurable compute resources (2-8 CPU cores, 2-8GB RAM) +- Git repository cloning (defaults to aws-rag-prototyping repo) +- Code-server and Claude Code modules +- Streamlit app preview on port 8501 + +### Database (`aws-aurora/aurora-pgvector.tf`) +- Aurora PostgreSQL 16.6 Serverless v2 cluster +- pgvector extension for vector embeddings +- Configurable scaling (0.5-1.0 ACU) +- Security group allowing PostgreSQL access + +## Environment Variables + +```bash +CLAUDE_CODE_USE_BEDROCK=1 +ANTHROPIC_MODEL=us.anthropic.claude-3-7-sonnet-20250219-v1:0 +PGVECTOR_HOST= +PGVECTOR_DATABASE=mydb1 +PGVECTOR_USER=dbadmin +``` + +## Usage + +1. Deploy template to Coder instance +2. Create workspace with desired CPU/memory configuration +3. Claude Code automatically sets up Python environment and installs dependencies +4. Access Streamlit preview at the provided URL +5. Use integrated development tools for RAG application prototyping + +## Prerequisites + +- Kubernetes cluster with Coder deployment +- AWS VPC with private subnets +- Appropriate IAM permissions for Aurora and Bedrock services \ No newline at end of file diff --git a/templates/awshp-k8s-rag-with-claude-code/aws-aurora/aurora-pgvector.tf b/templates/awshp-k8s-rag-with-claude-code/aws-aurora/aurora-pgvector.tf new file mode 100644 index 0000000..9590735 --- /dev/null +++ b/templates/awshp-k8s-rag-with-claude-code/aws-aurora/aurora-pgvector.tf @@ -0,0 +1,124 @@ +# variables for Coder Workspace Reference +variable "workspace_name" { + type = string + default = "awsragproto" +} + +variable "eks_cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "coder-aws-cluster" +} + +#Variables for Aurora PostgreSQL Serverless v2 + +variable "database_name" { + description = "Name of the database to be created" + type = string + default = "mydb" +} +variable "db_master_username" { + description = "Master username for the database" + type = string + default = "dbadmin" +} +variable "db_master_password" { + description = "Master password for the database" + type = string + default = "YourStrongPasswordHere1" # Consider using AWS Secrets Manager for production +} + +# Get EKS cluster info +data "aws_eks_cluster" "current" { + name = var.eks_cluster_name # Add this variable +} + +# Use EKS VPC +data "aws_vpc" "existing_vpc" { + id = data.aws_eks_cluster.current.vpc_config[0].vpc_id +} + +# Get private subnets from EKS +data "aws_subnets" "private" { + filter { + name = "vpc-id" + values = [data.aws_vpc.existing_vpc.id] + } + + tags = { + "kubernetes.io/role/internal-elb" = "1" + } +} + +# Create a subnet group for Aurora instances using existing subnets +resource "aws_db_subnet_group" "awsrag_aurora_subnet_group" { + name = "${var.workspace_name}-sgrp" + subnet_ids = data.aws_subnets.private.ids + + tags = { + Name = "${var.workspace_name}-sgrp" + } +} + +# Create security group for Aurora instances +resource "aws_security_group" "awsrag_aurora_sg" { + name = "${var.workspace_name}-sg" + description = "Security group for Aurora PostgreSQL instances" + vpc_id = data.aws_vpc.existing_vpc.id + + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Allow public access not restricted to the VPC + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.workspace_name}-sg" + } +} + +# First Aurora PostgreSQL Serverless v2 instance +resource "aws_rds_cluster" "awsrag_aurora_postgres_1" { + cluster_identifier = "${var.workspace_name}-pgvector01" + engine = "aurora-postgresql" + engine_mode = "provisioned" + engine_version = "16.6" + database_name = var.database_name + master_username = var.db_master_username + master_password = var.db_master_password # Use AWS Secrets Manager in production + db_subnet_group_name = aws_db_subnet_group.awsrag_aurora_subnet_group.name + vpc_security_group_ids = [aws_security_group.awsrag_aurora_sg.id] + skip_final_snapshot = true + + serverlessv2_scaling_configuration { + min_capacity = 0.5 + max_capacity = 1.0 + } +} + +# Primary DB instance for the Aurora PostgreSQL cluster +resource "aws_rds_cluster_instance" "awsrag_aurora_primary" { + cluster_identifier = aws_rds_cluster.awsrag_aurora_postgres_1.id + instance_class = "db.serverless" + engine = "aurora-postgresql" + engine_version = "16.6" + db_subnet_group_name = aws_db_subnet_group.awsrag_aurora_subnet_group.name + identifier = "${var.workspace_name}-primary" +} + +# Outputs +output "aurora_postgres_1_endpoint" { + value = aws_rds_cluster.awsrag_aurora_postgres_1.endpoint +} + +output "aurora_postgres_1_reader_endpoint" { + value = aws_rds_cluster.awsrag_aurora_postgres_1.reader_endpoint +} diff --git a/templates/awshp-k8s-rag-with-claude-code/main.tf b/templates/awshp-k8s-rag-with-claude-code/main.tf new file mode 100644 index 0000000..a203f8f --- /dev/null +++ b/templates/awshp-k8s-rag-with-claude-code/main.tf @@ -0,0 +1,456 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.37.1" + } + coder = { + source = "coder/coder" + version = "2.8.0" + } + random = { + source = "hashicorp/random" + version = "3.7.2" + } + } +} + +variable "eks_cluster_name" { + type = string + description = "The AWS EKS Kubernetes cluster name that Coder is deployed within." +} + +variable "namespace" { + type = string + description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace." +} + +data "coder_parameter" "cpu" { + name = "cpu" + display_name = "CPU" + description = "The number of CPU cores" + default = "2" + icon = "/icon/memory.svg" + mutable = true + option { + name = "2 Cores" + value = "2" + } + option { + name = "4 Cores" + value = "4" + } +} + +data "coder_parameter" "memory" { + name = "memory" + display_name = "Memory" + description = "The amount of memory in GB" + default = "4" + icon = "/icon/memory.svg" + mutable = true + option { + name = "4 GB" + value = "4" + } + option { + name = "6 GB" + value = "6" + } +} + +data "coder_parameter" "home_disk_size" { + name = "home_disk_size" + display_name = "Home disk size" + description = "The size of the home disk in GB" + default = "20" + type = "number" + icon = "/emojis/1f4be.png" + mutable = false + validation { + min = 1 + max = 99999 + } +} + +data "coder_parameter" "ai_prompt" { + type = "string" + name = "AI Prompt" + icon = "/emojis/1f4ac.png" + description = "Create a task prompt for Claude Code" + default = "Look for an AWS RAG Prototyping repo in the Coder Workspace. If found, create a new Python3 virtual environment, pip install the requirements.txt and then start the app via streamlit." + mutable = false +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = local.home_folder + startup_script = <<-EOT + set -e + sudo apt update + sudo apt install -y curl unzip postgresql-client telnet + + # install AWS CLI + if [ ! -d "aws" ]; then + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + sudo ./aws/install + aws --version + rm awscliv2.zip + fi + + # install AWS CDK + if ! command -v cdk &> /dev/null; then + echo "Installing AWS CDK..." + # Install Node.js and npm (required for CDK) + # Add NodeSource repository for the latest LTS version + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install nodejs -y + sudo npm install -g npm@11.3.0 + + # Verify installation + node -v + npm -v + + # Install AWS CDK globally + sudo npm install -g aws-cdk + + # Verify CDK installation + cdk --version + + echo "AWS CDK installation completed" + else + echo "AWS CDK is already installed" + cdk --version + fi + + # Enable Vector extension on Aurora PostgreSQL instance + PGPASSWORD="YourStrongPasswordHere1" psql -h ${module.aurora-pgvector.aurora_postgres_1_endpoint} -U dbadmin -d mydb1 -c "CREATE EXTENSION IF NOT EXISTS vector;" + + EOT + + env = { + CODER_MCP_CLAUDE_TASK_PROMPT = local.task_prompt + CODER_MCP_CLAUDE_SYSTEM_PROMPT = local.system_prompt + CLAUDE_CODE_USE_BEDROCK = "1" + ANTHROPIC_MODEL = "us.anthropic.claude-3-7-sonnet-20250219-v1:0" + ANTHROPIC_SMALL_FAST_MODEL = "us.anthropic.claude-3-5-haiku-20241022-v1:0" + CODER_MCP_APP_STATUS_SLUG = "claude-code" + PGVECTOR_USER = "dbadmin" + PGVECTOR_PASSWORD = "YourStrongPasswordHere1" + PGVECTOR_HOST = module.aurora-pgvector.aurora_postgres_1_endpoint + PGVECTOR_PORT = "5432" + PGVECTOR_DATABASE = "mydb1" + } + display_apps { + vscode = false + vscode_insiders = false + web_terminal = true + ssh_helper = false + } +} + +module "coder-login" { + source = "registry.coder.com/coder/coder-login/coder" + version = "1.0.15" + agent_id = coder_agent.dev.id +} + +# Prompt the user for the git repo URL +data "coder_parameter" "git_repo" { + name = "git_repo" + display_name = "Git repository" + default = "https://github.com/greg-the-coder/aws-rag-prototyping.git" +} + +# Clone the repository +module "git_clone" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/git-clone/coder" + version = "1.1.1" + agent_id = coder_agent.dev.id + url = data.coder_parameter.git_repo.value +} + +# Create a code-server instance for the cloned repository +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/code-server/coder" + version = "1.0.18" + agent_id = coder_agent.dev.id + order = 1 + folder = "/home/coder" +} + +module "claude-code" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/claude-code/coder" + version = "2.2.0" + agent_id = coder_agent.dev.id + folder = local.home_folder + subdomain = false + + install_claude_code = true + order = 999 + + experiment_report_tasks = true + experiment_pre_install_script = <<-EOF + # If user doesn't have a Github account or aren't + # part of the coder-contrib organization, then they can use the `coder-contrib-bot` account. + if [ ! -z "$GH_USERNAME" ]; then + unset -v GIT_ASKPASS + unset -v GIT_SSH_COMMAND + fi + EOF +} + +module "kiro" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/kiro/coder" + version = "1.1.0" + agent_id = coder_agent.dev.id + folder = "/home/coder" +} + +resource "coder_app" "preview" { + agent_id = coder_agent.dev.id + slug = "preview" + display_name = "Preview your app" + icon = "${data.coder_workspace.me.access_url}/emojis/1f50e.png" + url = "http://localhost:8501" + share = "authenticated" + subdomain = false + open_in = "tab" + order = 3 + healthcheck { + url = "http://localhost:8501/" + interval = 5 + threshold = 15 + } +} + +locals { + cost = 2 + region = "us-east-2" + home_folder = "/home/coder" +} + +locals { + port = 8501 + domain = element(split("/", data.coder_workspace.me.access_url), -1) +} + +locals { + task_prompt = join(" ", [ + "First, post a 'task started' update to Coder.", + "Then, review all of your memory.", + "Finally, ${data.coder_parameter.ai_prompt.value}.", + ]) + system_prompt = <<-EOT + Hey! First, report an initial task to Coder to show you have started! The user has provided you with a prompt of something to create. Create it the best you can, and keep it as succinct as possible. + + If you're being tasked to create a web application, then: + - ALWAYS start the server using `python3` or `node` on localhost:${local.port}. + - BEFORE starting the server, ALWAYS attempt to kill ANY process using port ${local.port}, and then run the dev server on port ${local.port}. + - ALWAYS build the project using dev servers (and ALWAYS VIA desktop-commander) + - When finished, you should use Playwright to review the HTML to ensure it is working as expected. + + ALWAYS run long-running commands (e.g. `pnpm dev` or `npm run dev`) using desktop-commander so it runs it in the background and users can prompt you. Other short-lived commands (build, test, cd, write, read, view, etc) can run normally. + + NEVER run the dev server without desktop-commander. + + For previewing, always use the dev server for fast feedback loops (never do a full Next.js build, for exmaple). A simple HTML/static is preferred for web applications, but pick the best AND lightest framework for the job. + + The dev server will ALWAYS be on localhost:${local.port} and NEVER start on another port. If the dev server crashes for some reason, kill port ${local.port} (or the desktop-commander session) and restart the dev server. + + After large changes, use Playwright to ensure your changes work (preview localhost:${local.port}). Take a screenshot, look at the screenshot. Also look at the HTML output from Playwright. If there are errors or something looks "off," fix it. + + Aim to autonomously investigate and solve issues the user gives you and test your work, whenever possible. + + Avoid shortcuts like mocking tests. When you get stuck, you can ask the user but opt for autonomy. + + In your task reports to Coder: + - Be specific about what you're doing + - Clearly indicate what information you need from the user when in "failure" state + - Keep it under 160 characters + - Make it actionable + + If you're being tasked to create a Coder template, then, + - You must ALWAYS ask the user for permission to push it. + - You are NOT allowed to push templates OR create workspaces from them without the users explicit approval. + + When reporting URLs to Coder, report to "https://preview--dev--${data.coder_workspace.me.name}--${data.coder_workspace_owner.me.name}.${local.domain}/" that proxies port ${local.port} + EOT +} + +resource "kubernetes_persistent_volume_claim" "home" { + metadata { + name = "coder-${data.coder_workspace.me.id}-home" + namespace = var.namespace + labels = { + "app.kubernetes.io/name" = "coder-pvc" + "app.kubernetes.io/instance" = "coder-pvc-${data.coder_workspace.me.id}" + "app.kubernetes.io/part-of" = "coder" + //Coder-specific labels. + "com.coder.resource" = "true" + "com.coder.workspace.id" = data.coder_workspace.me.id + "com.coder.workspace.name" = data.coder_workspace.me.name + "com.coder.user.id" = data.coder_workspace_owner.me.id + "com.coder.user.username" = data.coder_workspace_owner.me.name + } + annotations = { + "com.coder.user.email" = data.coder_workspace_owner.me.email + } + } + wait_until_bound = false + spec { + access_modes = ["ReadWriteOnce"] + resources { + requests = { + storage = "${data.coder_parameter.home_disk_size.value}Gi" + } + } + } +} + +resource "kubernetes_deployment" "dev" { + count = data.coder_workspace.me.start_count + depends_on = [ + kubernetes_persistent_volume_claim.home + ] + wait_for_rollout = false + metadata { + name = "coder-${data.coder_workspace.me.id}" + namespace = var.namespace + labels = { + "app.kubernetes.io/name" = "coder-workspace" + "app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}" + "app.kubernetes.io/part-of" = "coder" + "com.coder.resource" = "true" + "com.coder.workspace.id" = data.coder_workspace.me.id + "com.coder.workspace.name" = data.coder_workspace.me.name + "com.coder.user.id" = data.coder_workspace_owner.me.id + "com.coder.user.username" = data.coder_workspace_owner.me.name + } + annotations = { + "com.coder.user.email" = data.coder_workspace_owner.me.email + } + } + + spec { + replicas = 1 + selector { + match_labels = { + "app.kubernetes.io/name" = "coder-workspace" + "app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}" + "app.kubernetes.io/part-of" = "coder" + "com.coder.resource" = "true" + "com.coder.workspace.id" = data.coder_workspace.me.id + "com.coder.workspace.name" = data.coder_workspace.me.name + "com.coder.user.id" = data.coder_workspace_owner.me.id + "com.coder.user.username" = data.coder_workspace_owner.me.name + } + } + strategy { + type = "Recreate" + } + + template { + metadata { + labels = { + "app.kubernetes.io/name" = "coder-workspace" + "app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}" + "app.kubernetes.io/part-of" = "coder" + "com.coder.resource" = "true" + "com.coder.workspace.id" = data.coder_workspace.me.id + "com.coder.workspace.name" = data.coder_workspace.me.name + "com.coder.user.id" = data.coder_workspace_owner.me.id + "com.coder.user.username" = data.coder_workspace_owner.me.name + } + } + spec { + security_context { + run_as_user = 1000 + fs_group = 1000 + } + service_account_name = "coder" + container { + name = "dev" + image = "codercom/enterprise-base:ubuntu" + image_pull_policy = "Always" + command = ["sh", "-c", coder_agent.dev.init_script] + security_context { + run_as_user = "1000" + } + env { + name = "CODER_AGENT_TOKEN" + value = coder_agent.dev.token + } + resources { + requests = { + "cpu" = "250m" + "memory" = "512Mi" + } + limits = { + "cpu" = "${data.coder_parameter.cpu.value}" + "memory" = "${data.coder_parameter.memory.value}Gi" + } + } + volume_mount { + mount_path = "/home/coder" + name = "home" + read_only = false + } + } + + volume { + name = "home" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim.home.metadata.0.name + read_only = false + } + } + + affinity { + // This affinity attempts to spread out all workspace pods evenly across + // nodes. + pod_anti_affinity { + preferred_during_scheduling_ignored_during_execution { + weight = 1 + pod_affinity_term { + topology_key = "kubernetes.io/hostname" + label_selector { + match_expressions { + key = "app.kubernetes.io/name" + operator = "In" + values = ["coder-workspace"] + } + } + } + } + } + } + } + } + } +} + +module "aurora-pgvector" { + source = "./aws-aurora" + + workspace_name = data.coder_workspace.me.name + eks_cluster_name = var.eks_cluster_name + db_master_username = "dbadmin" + db_master_password = "YourStrongPasswordHere1" + database_name = "mydb1" +} + +resource "coder_metadata" "pod_info" { + count = data.coder_workspace.me.start_count + resource_id = kubernetes_deployment.dev[0].id + daily_cost = local.cost +} diff --git a/templates/template_versions.tf b/templates/template_versions.tf index ffd38b7..a0e077f 100644 --- a/templates/template_versions.tf +++ b/templates/template_versions.tf @@ -99,3 +99,24 @@ resource "coderd_template" "awshp-windows-dcv" { name = var.coder_gitsha }] } + +resource "coderd_template" "awshp-k8s-rag-with-claude-code" { + name = "awshp-k8s-rag-with-claude-code" + display_name = "AWS Workshop Kubernetes AWS RAG Prototyping with Claude Code" + description = "Provision Kubernetes Deployments as Coder workspaces with Anthropic Claude Code for AWS RAG prototyping." + icon = "/icon/k8s.png" + versions = [{ + directory = "./awshp-k8s-rag-with-claude-code" + active = true + # Version name is optional + name = var.coder_gitsha + tf_vars = [{ + name = "namespace" + value = "coder" + }, + { + name = "eks_cluster_name" + value = "coder-aws-cluster" + }] + }] +}