Skip to content

Self-Hosted Deployment Guide


Overview

Deploy CloudAEye Code Review in your AWS account with the CloudAEye Terraform configuration. The stack creates the network, ECS Managed Instances cluster, application services, data services, authentication service, queues, and logging required for a self-hosted deployment.

Prerequisites

Before you deploy, confirm you have:

What Gets Created

Resource Purpose
VPC, public subnet, private subnet Isolated network for the deployment
Internet gateway and NAT gateway Public load balancer access and private ECS outbound access
Application Load Balancer Public entry point for CloudAEye routes
ECS cluster with ECS Managed Instances Runtime for application containers and data services
MongoDB ECS service Internal database for CloudAEye application data
ChromaDB ECS service Internal vector database
ECS managed EBS volumes Persistent storage for MongoDB and ChromaDB
Cloud Map private DNS Internal service discovery for MongoDB and ChromaDB
SQS queues and dead-letter queues Queues asynchronous indexing and review work; failed messages move to dedicated 14-day dead-letter queues after five receives
Better Auth ECS service Tenant registration, login, OAuth, 2-step verification, password management, and enterprise SSO
CloudWatch log groups and alarms Logs for application, data, initialization, and cleanup tasks; alarms cover queue backlogs, dead-letter queues, pending ECS tasks, and unhealthy load balancer targets
EventBridge Scheduler cleanup job Runs the private cloudaeye-cleanup task every day at 00:00 UTC to remove expired code-review data

MongoDB and ChromaDB are private services and are not exposed through the public load balancer.

Terraform Bundle Files

CloudAEye provides the deployment bundle as a ZIP file. It contains the deployable Terraform configuration, example inputs, release metadata, and the initializer helper required during the first apply.

File What it contains
terraform.tf Terraform CLI and provider version constraints.
providers.tf AWS provider configuration, including the selected region and local AWS profile.
variables.tf Inputs for AWS, networking, HTTPS, tenant initialization, GitHub App authentication, licensing, LLM settings, ECS sizing, storage, service overrides, and autoscaling.
locals.tf Derived names, release-pinned container images, default ECS service definitions, environment variables, public routes, capacity-provider sizing, and shared tags.
main.tf VPC networking, security groups, SQS source queues and dead-letter queues, Cloud Map service discovery, and HTTP/HTTPS load balancer listeners and routing.
iam.tf IAM roles and least-privilege policies for ECS execution and application tasks, managed instances, EBS volumes, SQS, authentication email, and license-agent runtime inspection.
ecs.tf ECS cluster and Managed Instances capacity providers; MongoDB, ChromaDB, license-agent, application, and Fargate-overflow services; persistent volumes; autoscaling; and the one-time initializer task.
cleanup.tf Daily EventBridge Scheduler job, ECS task definition, IAM permissions, logs, and outputs for the private cloudaeye-cleanup task.
observability.tf CloudWatch alarms for non-empty dead-letter queues, old source-queue messages, sustained pending ECS tasks, and unhealthy load balancer targets. The alarms have no notification action by default.
outputs.tf ECS names and role ARNs, application URL, public routes, internal data-service endpoints, storage sizes, and source/dead-letter queue URLs and ARNs. Cleanup outputs are declared in cleanup.tf.
terraform.tfvars.example Example inputs to copy to a local, uncommitted terraform.tfvars file.
onprem-release.auto.tfvars.json Build-generated release pin. Terraform loads it automatically so all CloudAEye images use the release represented by the ZIP. Do not edit it.
onprem-release-manifest.json Build-generated record of the release tag and the exact CloudAEye image references packaged for that release.
.terraform.lock.hcl Provider dependency selections used to produce a reproducible Terraform installation.
run_cloudaeye_initialize.py Helper invoked by Terraform during the first apply to run the initializer ECS task, wait for it to stop, and fail the apply with ECS diagnostics when initialization is unsuccessful.
README.md Concise deployment and operations reference included with the bundle.

The release bundle intentionally does not contain terraform.tfvars, Terraform state, or CloudAEye's internal release-packaging script. Keep your generated terraform.tfvars and state files private.

Step 1: Prepare The Terraform Bundle

Extract the downloaded Terraform ZIP, then go to the extracted deployment directory:

cd /path/to/on-prem-deployment

Create your local variables file:

cp terraform.tfvars.example terraform.tfvars

Sample variable file:

aws_region  = "us-east-2" // Replace "us-east-2" with actual value
aws_profile = "default"   // Replace "default" with actual value
app_name    = "cloudaeye" // Replace "cloudaeye" with actual value

# Restrict this to your office IP range.(Optional)
allowed_app_cidr = "0.0.0.0/0" // Replace "0.0.0.0/0" with actual value

mongodb_password = "password"         // Replace "password" with actual value
server_x_api_key = "server-x-api-key" // Replace "server-x-api-key" with actual value

# CloudAEye-hosted LLM access is the default. Set llm_host_type to
# "your_own" only when supplying BYO credentials below.
llm_host_type = "cloudaeye" // Replace "cloudaeye" with actual value
llm_provider  = "OpenAI"    // Replace "OpenAI" with actual value
llm_model     = "gpt-5.4"   // Replace "gpt-5.4" with actual value

# BYO LLM credentials. Uncomment only the values required by llm_provider.
# OpenAI:
# llm_openai_api_key = "openai-api-key" // Replace "openai-api-key" with actual value
# Anthropic:
# llm_anthropic_api_key = "anthropic-api-key" // Replace "anthropic-api-key" with actual value
# AWS_Bedrock:
# llm_aws_bedrock_access_key = "aws-bedrock-access-key" // Replace "aws-bedrock-access-key" with actual value
# llm_aws_bedrock_secret_key = "aws-bedrock-secret-key" // Replace "aws-bedrock-secret-key" with actual value
# llm_aws_bedrock_region     = "us-east-2"               // Replace "us-east-2" with actual value
# GCP_Vertex:
# llm_gcp_service_account_json = "{\"type\":\"service_account\"}" // Replace the example JSON with actual value
# Azure:
# llm_azure_api_key     = "azure-api-key"                     // Replace "azure-api-key" with actual value
# llm_azure_endpoint    = "https://example.openai.azure.com"  // Replace the example URL with actual value
# llm_azure_api_version = "2024-02-15-preview"                 // Replace "2024-02-15-preview" with actual value

server_aws_access_key_id     = "server-aws-access-key-id"     // Replace "server-aws-access-key-id" with actual value
server_aws_secret_access_key = "server-aws-secret-access-key" // Replace "server-aws-secret-access-key" with actual value

tenant_key              = "1"                 // Replace "1" with actual value
tenant_identifier       = "company"          // Replace "company" with actual value
tenant_admin_email      = "admin@example.com" // Replace "admin@example.com" with actual value
tenant_admin_password   = "TestPassword@123"  // Replace "TestPassword@123" with actual value
tenant_admin_company    = "Example Company"   // Replace "Example Company" with actual value
tenant_admin_first_name = "Jane"              // Replace "Jane" with actual value
tenant_admin_last_name  = "Admin"             // Replace "Admin" with actual value

# Authentication email through your pre-verified SES identity.
ses_identity_arn = "arn:aws:ses:us-east-2:123456789012:identity/example.com" // Replace the example ARN with actual value
ses_from_email   = "CloudAEye <no-reply@example.com>"                        // Replace the example sender with actual value

# CloudAEye-issued on-prem license token.
license_token = "cla_live_replace-with-cloudAEye-issued-license-token" // Replace the example token with actual value

# Required by on-prem GitHub App auth and webhook handling.
github_app_key         = "your-app-key" // Replace "your-app-key" with actual value
github_installation_id = "12345678"         // Replace "12345678" with actual value
github_app_id          = "123456"           // Replace "123456" with actual value
# Set exactly one of github_user or github_organization.
# github_organization = "my_org" // Replace "my_org" with actual value
github_user = "your-github-user" // Replace "your-github-user" with actual value
# Replace the example private key below with actual value.
github_private_key    = <<EOT
-----BEGIN RSA PRIVATE KEY-----
replace-with-your-github-app-private-key
-----END RSA PRIVATE KEY-----
EOT
github_webhook_secret = "replace-with-github-webhook-secret" // Replace the example secret with actual value

Step 2: Configure Terraform Variables

Edit terraform.tfvars and set the exact values:

Understanding the source labels

The Source column identifies who chooses, provides, or creates each value:

Source What it means
CloudAEye CloudAEye provides the value. Contact CloudAEye if you have not received it.
Generate You create a new strong random value. Generate a separate value for every secret.
AWS Obtain the value from your AWS account.
GitHub Obtain the value from the GitHub App settings or installation details.
LLM provider Obtain the value from the selected model provider's console, such as OpenAI, Anthropic, AWS, Google Cloud, or Azure.
You Choose or supply the value based on your deployment, organization, or initial admin user.

AWS and deployment basics

Variable Source How to obtain the value Example
aws_region You Choose the AWS region where you want to deploy. us-east-2
aws_profile You Use the profile configured by aws configure. Use default if you configured the default profile. default
app_name You Choose a prefix for the resources created by this deployment. cloudaeye
allowed_app_cidr You Use your public IP address with a /32 suffix. 203.0.113.10/32

Generated secrets

Generate a different strong random value for each secret.

Variable Source How to obtain the value Example format
mongodb_password Generate Generate a strong random password for the private MongoDB service. <64-character-random-hex-string>
server_x_api_key Generate Generate a strong random API key for communication with the CloudAEye application containers. <64-character-random-hex-string>
github_webhook_secret Generate Generate a strong random secret. Enter this exact value in both terraform.tfvars and the GitHub App webhook configuration. <64-character-random-hex-string>

AWS credentials for the application runtime

These credentials are used by the deployed CloudAEye services. They are separate from the local aws_profile that Terraform uses during deployment.

Variable Source How to obtain the value Example format
server_aws_access_key_id AWS In the AWS console, open IAM > Users > your application user > Security credentials, then create an access key. <AWS-access-key-id>
server_aws_secret_access_key AWS Copy the secret access key when AWS displays it after creating the access key. AWS displays this value only once. <AWS-secret-access-key>

LLM configuration

Variable Source How to obtain the value Example
llm_host_type You Choose cloudaeye for CloudAEye-hosted models, which is the default, or your_own when supplying your own provider credentials. cloudaeye
llm_provider You Choose one of OpenAI, Anthropic, AWS_Bedrock, GCP_Vertex, or Azure. OpenAI
llm_model You Enter a model supported by the selected provider. gpt-5.4
llm_* provider settings LLM provider Required only with llm_host_type = "your_own". Obtain the API key, credentials, endpoint, region, or service account details from your provider console. <OpenAI-key>

Tenant and initial admin

Variable Source How to obtain the value Example
tenant_key CloudAEye Assigned by CloudAEye during tenant and license provisioning. 12345
tenant_identifier CloudAEye Assigned by CloudAEye during tenant and license provisioning. acme-corp
tenant_admin_email You Enter the email address of your first admin user. Use this when logging into the CloudAEye console. admin@example.com
tenant_admin_password You It must include uppercase and lowercase letters, a number, and a symbol, and be at least 8 characters long. Use this when logging into the CloudAEye console. <strong-admin-password>
tenant_admin_company You Enter your company name. Acme Corporation
tenant_admin_first_name, tenant_admin_last_name You Enter the first and last name of the initial user. John, Smith

License

Variable Source How to obtain the value Example format
license_token CloudAEye Use the token emailed to you by CloudAEye. Production tokens start with cla_live_. <issued-token>

GitHub App

Variable Source How to obtain the value Example
github_app_id GitHub Open the GitHub App settings page and copy App ID. 123456
github_app_key GitHub Use a random generated value. cloudaeye-code-review-acme
github_installation_id GitHub Copy the numeric ID from the installation URL: .../settings/installations/<ID>. 6666666
github_user GitHub For a personal-account installation, enter the account username. Set exactly github_user. octocat
github_organization GitHub For an organization installation, enter the organization name. Set exactly github_organization. acme-corp
github_private_key GitHub In the GitHub App settings, select Private keys > Generate a private key. Paste the complete contents of the downloaded .pem file. -----BEGIN RSA PRIVATE KEY-----...
github_webhook_secret GitHub Use the same generated random secret that you entered in the GitHub App webhook configuration. <64-character-random-hex-string>

Authentication email settings

Set both values to send verification, invitation, password-reset, and security emails through a verified Amazon SES identity.

Variable Source How to obtain the value Example
ses_identity_arn AWS Copy the ARN of the verified identity from Amazon SES in the deployment region. arn:aws:ses:us-east-2:123456789012:identity/example.com
ses_from_email You / AWS Enter the sender name and an email address allowed by that SES identity. <no-reply@example.com>

Step 3: Deploy

Run Terraform:

terraform init
terraform validate
terraform plan
terraform apply

Review the plan before applying. On the first apply, Terraform creates the infrastructure, starts the ECS services, and runs a one-time authentication bootstrap task after Better Auth and the other application services are ready.

The bootstrap task creates the Better Auth tenant owner and credential account and initializes the tenant's product settings in MongoDB. For registration, OAuth, 2-step verification, password lifecycle, and password-policy details, see the CloudAEye authentication guide.

Step 4: Access CloudAEye

After terraform apply completes, get the application URL:

terraform output -raw app_base_url

The command prints the deployed application URL. Open the CloudAEye UI by appending /ui to that URL.

You can also inspect the load balancer DNS name and public routes:

terraform output alb_dns_name
terraform output service_routes

Public paths:

Route Service
/ui/* cloudaeye-web
/code-review/* cloudaeye-reviews
/rca/test/* cloudaeye-api
/rca/code-repository-integration/* cloudaeye-repositories
/external-service/* cloudaeye-indexer-api
/secret-scanner/* cloudaeye-secrets
/knowledge-graph cloudaeye-knowledge
/auth/* tenant-registration-v2
/github-app/webhook cloudaeye-webhook

The cloudaeye-indexer-worker, cloudaeye-cleanup, MongoDB, and ChromaDB services are private and are not exposed through the load balancer. The cloudaeye-cleanup task runs once per day and removes old data from MongoDB.

Upgrade An HTTP Deployment To HTTPS

You can deploy over HTTP first and add HTTPS after the load balancer exists. The upgrade requires a DNS name you own and an issued ACM certificate; an ACM certificate cannot provide trusted HTTPS for the AWS-generated *.elb.amazonaws.com hostname.

1. Get the load balancer hostname

From the Terraform deployment directory, run:

terraform output -raw alb_dns_name

Save the returned hostname, for example cloudaeye-example.us-east-2.elb.amazonaws.com.

2. Get the certificate ARN

Use an existing public certificate in AWS Certificate Manager (ACM) that covers your CloudAEye hostname. The certificate must have the Issued status and be in the same AWS account and region as the load balancer.

Open the certificate in ACM and copy its ARN. You will add this value to terraform.tfvars in Step 4.

If you do not already have a suitable certificate, follow the AWS instructions for requesting a public certificate. Return to this guide after ACM issues the certificate.

3. Point your hostname to the load balancer

Create the application DNS record at your DNS provider:

Record Name Value
CNAME cloudaeye.example.com The value returned by terraform output -raw alb_dns_name

Squarespace DNS example

If Squarespace manages DNS for example.com:

  1. Open the Squarespace Domains dashboard and select example.com.
  2. Choose DNS, then DNS Settings.
  3. Under Custom Records, choose Add record.
  4. Add the following record:
Squarespace field Example value
Type CNAME
Name cloudaeye
Data cloudaeye-example.us-east-2.elb.amazonaws.com
TTL Default

Replace the example Data with the value returned by terraform output -raw alb_dns_name. Squarespace automatically appends .example.com to the Name, so entering cloudaeye creates cloudaeye.example.com. See Squarespace DNS record troubleshooting if the record does not save.

For an apex domain such as example.com, use your DNS provider's ALIAS, ANAME, or CNAME-flattening feature instead of a standard CNAME. A subdomain such as cloudaeye.example.com is simpler because it can use a standard CNAME. Wait for your hostname to resolve to the load balancer before continuing.

4. Enable HTTPS in Terraform

Add both values to terraform.tfvars:

app_domain_name      = "cloudaeye.example.com"
acm_certificate_arn = "arn:aws:acm:us-east-2:123456789012:certificate/00000000-0000-0000-0000-000000000000"

Use the complete ARN of the issued certificate. Then review and apply the change:

terraform validate
terraform plan
terraform apply

Expect the plan to update the load balancer listeners and ECS task definitions.

When both values are set, Terraform adds an HTTPS listener on port 443 and redirects HTTP port 80 to your configured hostname over HTTPS while preserving the path and query string. Leave both values empty to keep an HTTP-only deployment; setting only one value is invalid.

5. Verify the upgrade

Check the canonical URL and the HTTP redirect:

terraform output -raw app_base_url
curl -I "http://cloudaeye.example.com/ui"
curl -I "https://cloudaeye.example.com/ui"

The first request should return a 301 redirect to HTTPS. The second should complete a TLS handshake and return an application response. Open <app_base_url>/ui and verify the GitHub App webhook after updating its URL to <app_base_url>/github-app/webhook.

Configuration

Most configuration specific to your deployment is handled through terraform.tfvars.

To restrict access to the public application, set allowed_app_cidr to your office or CIDR:

allowed_app_cidr = "203.0.113.10/32"

For advanced deployments, the ecs_services map can override service images, CPU, memory, desired count, commands, route paths, and health check paths. Use this only after consulting CloudAEye.

Operations

List ECS services:

aws ecs list-services \
  --cluster "$(terraform output -raw ecs_cluster_name)"

Describe a service:

aws ecs describe-services \
  --cluster "$(terraform output -raw ecs_cluster_name)" \
  --services <service-name>

CloudWatch log groups are created under:

/ecs/<app_name>-<environment>/<service-name>

Destroy

To remove the deployment:

terraform destroy

This deletes the infrastructure created by the stack, including ECS services and the EBS data volumes. Export or back up any required data before destroying the deployment.