Skip to main content
Browse docs
By Audience
Getting Started
Configuration
Use Cases
IDE Integration
Third-Party Integrations
Engineering Cache
Console
API Reference
Gateway
Workflow Guides
Templates
Providers and SDKs
Industry Guides
Advanced Guides
Browse by Role
Deployment Guides
In-Depth Guides
Tutorials
FAQ

Deploy Keeptrusts on AWS

This guide walks through a production-grade AWS deployment of the Keeptrusts platform using ECS Fargate for compute, RDS for Postgres, ALB for load balancing, and supporting services for monitoring, storage, and security.

Use this page when

  • You are deploying the Keeptrusts gateway and API to AWS for the first time.
  • You need Terraform/IaC patterns for ECS Fargate, RDS PostgreSQL, ALB, and S3 exports.
  • You are migrating from Docker Compose to a production-grade AWS topology.
  • You need to set up VPC networking, security groups, and IAM roles for Keeptrusts services.

Primary audience

  • Primary: Technical Engineers
  • Secondary: AI Agents, Technical Leaders

Architecture overview

Internet
→ Route 53 (DNS)
→ ALB (Application Load Balancer)
→ Target Group: Keeptrusts Gateway (port 41002)
→ Target Group: Keeptrusts API (port 8080)
→ ECS Fargate Service: Gateway
→ ECS Fargate Service: API
→ ECS Fargate Service: Workers (export, lifecycle, config)
→ RDS PostgreSQL (private subnet)
→ S3 (export artifacts)
→ Secrets Manager (API keys, DB credentials)
→ CloudWatch (logs, metrics, alarms)

Prerequisites

  • AWS account with permissions for ECS, RDS, ALB, S3, IAM, VPC, Secrets Manager, CloudWatch
  • AWS CLI configured
  • Terraform or CloudFormation (this guide uses Terraform examples)
  • Keeptrusts container images pushed to ECR

VPC and networking

VPC layout

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"

name = "keeptrusts-vpc"
cidr = "10.0.0.0/16"

azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.11.0/24", "10.0.12.0/24"]

enable_nat_gateway = true
single_nat_gateway = false
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Environment = "production"
Service = "keeptrusts"
}
}

Security groups

resource "aws_security_group" "alb" {
name_prefix = "keeptrusts-alb-"
vpc_id = module.vpc.vpc_id

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_security_group" "ecs" {
name_prefix = "keeptrusts-ecs-"
vpc_id = module.vpc.vpc_id

ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_security_group" "rds" {
name_prefix = "keeptrusts-rds-"
vpc_id = module.vpc.vpc_id

ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.ecs.id]
}
}

RDS PostgreSQL

resource "aws_db_instance" "keeptrusts" {
identifier = "keeptrusts-db"
engine = "postgres"
engine_version = "16.4"
instance_class = "db.r6g.large"

allocated_storage = 100
max_allocated_storage = 500
storage_encrypted = true
storage_type = "gp3"

db_name = "keeptrusts"
username = "keeptrusts"
password = aws_secretsmanager_secret_version.db_password.secret_string

multi_az = true
db_subnet_group_name = aws_db_subnet_group.keeptrusts.name
vpc_security_group_ids = [aws_security_group.rds.id]

backup_retention_period = 30
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"

deletion_protection = true
skip_final_snapshot = false

performance_insights_enabled = true

tags = {
Service = "keeptrusts"
Environment = "production"
}
}

ECS Fargate services

Cluster

resource "aws_ecs_cluster" "keeptrusts" {
name = "keeptrusts"

setting {
name = "containerInsights"
value = "enabled"
}

configuration {
execute_command_configuration {
logging = "OVERRIDE"
log_configuration {
cloud_watch_log_group_name = aws_cloudwatch_log_group.ecs_exec.name
}
}
}
}

API service

resource "aws_ecs_task_definition" "api" {
family = "keeptrusts-api"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 1024
memory = 2048
execution_role_arn = aws_iam_role.ecs_execution.arn
task_role_arn = aws_iam_role.api_task.arn

container_definitions = jsonencode([{
name = "api"
image = "${aws_ecr_repository.api.repository_url}:latest"
portMappings = [{ containerPort = 8080, protocol = "tcp" }]
environment = [
{ name = "KEEPTRUSTS_PORT", value = "8080" },
{ name = "KEEPTRUSTS_CORS_ALLOWED_ORIGINS", value = "https://console.keeptrusts.com" },
]
secrets = [
{ name = "DATABASE_URL", valueFrom = aws_secretsmanager_secret.db_url.arn },
{ name = "KEEPTRUSTS_JWT_SECRET", valueFrom = "${aws_secretsmanager_secret.jwt.arn}:jwt_secret::" },
{ name = "KEEPTRUSTS_SECRET_ENCRYPTION_KEY", valueFrom = "${aws_secretsmanager_secret.encryption.arn}:key::" },
]
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 15
}
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.api.name
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "api"
}
}
}])
}

resource "aws_ecs_service" "api" {
name = "keeptrusts-api"
cluster = aws_ecs_cluster.keeptrusts.id
task_definition = aws_ecs_task_definition.api.arn
desired_count = 2
launch_type = "FARGATE"

network_configuration {
subnets = module.vpc.private_subnets
security_groups = [aws_security_group.ecs.id]
}

load_balancer {
target_group_arn = aws_lb_target_group.api.arn
container_name = "api"
container_port = 8080
}
}

Gateway service

resource "aws_ecs_task_definition" "gateway" {
family = "keeptrusts-gateway"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 512
memory = 1024
execution_role_arn = aws_iam_role.ecs_execution.arn
task_role_arn = aws_iam_role.gateway_task.arn

container_definitions = jsonencode([{
name = "gateway"
image = "${aws_ecr_repository.gateway.repository_url}:latest"
command = ["gateway", "run", "--listen", "0.0.0.0:41002", "--policy-config", "/etc/keeptrusts/policy-config.yaml"]
portMappings = [{ containerPort = 41002, protocol = "tcp" }]
environment = [
{ name = "KEEPTRUSTS_API_URL", value = "http://keeptrusts-api.internal:8080" },
]
secrets = [
{ name = "KEEPTRUSTS_GATEWAY_TOKEN", valueFrom = aws_secretsmanager_secret.gateway_api_key.arn },
]
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:41002/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 10
}
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.gateway.name
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "gateway"
}
}
}])
}

Application Load Balancer

resource "aws_lb" "keeptrusts" {
name = "keeptrusts-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = module.vpc.public_subnets

enable_deletion_protection = true
}

resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.keeptrusts.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.keeptrusts.arn

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
}

resource "aws_lb_listener_rule" "gateway" {
listener_arn = aws_lb_listener.https.arn
priority = 100

action {
type = "forward"
target_group_arn = aws_lb_target_group.gateway.arn
}

condition {
host_header { values = ["gateway.example.com"] }
}
}

S3 for exports

resource "aws_s3_bucket" "exports" {
bucket = "keeptrusts-exports-${var.environment}"

tags = {
Service = "keeptrusts"
Environment = var.environment
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "exports" {
bucket = aws_s3_bucket.exports.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.keeptrusts.arn
}
}
}

resource "aws_s3_bucket_lifecycle_configuration" "exports" {
bucket = aws_s3_bucket.exports.id

rule {
id = "expire-old-exports"
status = "Enabled"

expiration { days = 90 }

transition {
days = 30
storage_class = "STANDARD_IA"
}
}
}

IAM roles

resource "aws_iam_role" "api_task" {
name = "keeptrusts-api-task"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ecs-tasks.amazonaws.com" }
}]
})
}

resource "aws_iam_role_policy" "api_s3" {
name = "keeptrusts-api-s3"
role = aws_iam_role.api_task.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:PutObject", "s3:GetObject", "s3:ListBucket"]
Resource = [aws_s3_bucket.exports.arn, "${aws_s3_bucket.exports.arn}/*"]
}]
})
}

CloudWatch monitoring

resource "aws_cloudwatch_metric_alarm" "api_cpu" {
alarm_name = "keeptrusts-api-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 3
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = 60
statistic = "Average"
threshold = 80
alarm_description = "API CPU utilization exceeds 80%"
alarm_actions = [aws_sns_topic.alerts.arn]

dimensions = {
ClusterName = aws_ecs_cluster.keeptrusts.name
ServiceName = aws_ecs_service.api.name
}
}

resource "aws_cloudwatch_log_group" "api" {
name = "/ecs/keeptrusts-api"
retention_in_days = 90
}

resource "aws_cloudwatch_log_group" "gateway" {
name = "/ecs/keeptrusts-gateway"
retention_in_days = 90
}

Cost optimization

StrategyImplementation
Right-size tasksStart with 0.5 vCPU / 1 GB for gateway, scale based on metrics
Fargate SpotUse capacityProviderStrategy with Spot for non-critical workers
RDS Reserved InstancesCommit to 1-year RI for production database
S3 lifecycleTransition exports to IA after 30 days, expire after 90
NAT GatewayUse single NAT for dev, multi-AZ for production

Verification

After deployment, verify the stack:

# Check API health
curl -s https://api.keeptrusts.com/health | jq

# Check gateway health
curl -s https://gateway.keeptrusts.com/health | jq

# Validate the source configuration before rollout
kt policy lint --file policy-config.yaml

# Tail events to confirm the gateway is processing
kt events tail --limit 5 --format json

# Create a test export
kt export create --format json --window 1h

Troubleshooting

IssueCauseFix
ECS tasks fail to startSecrets Manager access deniedVerify execution role has secretsmanager:GetSecretValue
RDS connection refusedSecurity group misconfiguredEnsure ECS SG is allowed on port 5432 in RDS SG
ALB returns 502Target group health check failingCheck container health endpoint and port mapping
S3 export upload failsMissing IAM permissionsAdd s3:PutObject to the task role

For AI systems

  • Canonical terms: Keeptrusts gateway, Keeptrusts API, ECS Fargate deployment, RDS PostgreSQL, ALB, S3 exports, CloudWatch, Secrets Manager.
  • Key config: DATABASE_URL, KEEPTRUSTS_CORS_ALLOWED_ORIGINS, KEEPTRUSTS_EXPORT_S3_BUCKET, task definition CPU/memory, security group rules.
  • CLI commands: kt policy lint, kt events tail, kt export create.
  • Best next pages: Kubernetes deployment, Terraform IaC, Multi-region deployment.

For engineers

  • Prerequisites: AWS account with ECS/RDS/ALB/S3/IAM permissions, Terraform or CloudFormation, container images pushed to ECR.
  • Validate: curl https://api.keeptrusts.com/health, curl https://gateway.keeptrusts.com/health, kt events tail --limit 5.
  • Key sizing: Start gateway at 0.5 vCPU / 1 GB, API at 1 vCPU / 2 GB. Scale based on CloudWatch metrics.
  • Security: ECS tasks in private subnets, RDS accessible only from ECS security group, all secrets in Secrets Manager.

For leaders

  • Cost model: ECS Fargate (pay-per-task-second), RDS (instance + storage), ALB (per hour + LCU), S3 (storage + requests). Fargate Spot reduces worker costs by ~70%.
  • Compliance: Encryption at rest (RDS, S3), encryption in transit (TLS via ALB), VPC isolation, IAM least-privilege.
  • Operational ownership: Platform/DevOps team owns infrastructure; AI governance team owns policy configs.
  • DR: Multi-AZ RDS with 30-day backups, ECS service auto-recovery, S3 cross-region replication for exports.

Next steps