Deploy
Deploy a single node on AWS
Provision self-hosted camelAI on EC2 with Terraform or CloudFormation.
camelAI includes Terraform and CloudFormation templates for the supported single-node deployment. Both templates implement the same Docker Compose release contract.
They provision:
- An Ubuntu 24.04 EC2 instance with IMDSv2 required
- An encrypted gp3 data volume for the checkout, Docker data, and backups
- A security group that exposes only the web proxy and optional break-glass SSH
- SSM Session Manager access
- Least-privilege Secrets Manager reads and Route 53 DNS-challenge access
- An Elastic IP and optional Route53 records
- Caddy in front of bundled Pomerium
- A systemd service that pulls and starts the selected release
- Installation-time project-build, notebook, and database-query smokes
The default instance type is t3a.xlarge. The subnet and selected Availability
Zone must match.
Before you start
You need:
- AWS credentials that can create EC2, EBS, IAM, SSM, security-group, and optional Route53 resources
- A VPC and subnet with outbound internet access, or permission to use the default VPC
- A
selfhost-v*release manifest with seven digest-pinned camelAI images and the tested Pomerium digest - A main hostname, authenticate hostname, and deployed-app wildcard domain
- An OIDC application for bundled Pomerium
- A Route 53 hosted zone for the default automatic-TLS path
- Two AWS Secrets Manager secrets
Store each secret as its raw SecretString, not as a JSON object:
- The AI provider API key
- The Pomerium OIDC client secret
If your policy requires operator-provided certificates instead, create two additional secrets for the PEM certificate chain and matching unencrypted private key.
The AWS templates consume release images. Do not point a production stack at mutable tags or combine images from different releases.
Terraform
Clone and check out the selected release:
git clone https://github.com/qaml-ai/camelAI.git
cd camelAI
git checkout --detach selfhost-vX.Y.Z
cd infra/selfhost/terraform
cp terraform.tfvars.example terraform.tfvarsEdit terraform.tfvars. At minimum, replace:
repository_ref- All seven camelAI
*_imagevalues andpomerium_image - The main, authenticate, and wildcard hostnames
- Route53 settings, or disable automatic DNS creation
- The AI and Pomerium secret ARNs
tls_mode = "automatic"and the Route 53 hosted-zone ID- The OIDC issuer and client ID
Then run:
terraform init
terraform plan
terraform applyTerraform waits for the VM bootstrap marker through an SSM association. The apply fails if Compose startup, the health check, or any attached-runtime smoke fails.
Use the outputs to inspect the host:
aws ssm start-session --target <instance-id>
sudo cloud-init status --wait
sudo journalctl -u camelai-selfhost --no-pager
sudo camelai-selfhost-compose logs --tail=200 caddy pomerium appPrefer SSM Session Manager. Leave ssh_ingress_cidrs empty unless you need
narrowly scoped break-glass SSH access.
CloudFormation
Deploy the template into a subnet with outbound internet access:
aws cloudformation deploy \
--stack-name camelai-selfhost \
--template-file infra/selfhost/cloudformation/aws-single-node.yaml \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
VpcId=vpc-... \
SubnetId=subnet-... \
AvailabilityZone=us-east-1a \
RepositoryRef=selfhost-vX.Y.Z \
AppImage=ghcr.io/qaml-ai/camelai-selfhost-app@sha256:... \
LocalArtifactsImage=ghcr.io/qaml-ai/camelai-selfhost-local-artifacts@sha256:... \
ProjectBuildImage=ghcr.io/qaml-ai/camelai-selfhost-project-build@sha256:... \
AnalysisImage=ghcr.io/qaml-ai/camelai-selfhost-analysis@sha256:... \
DbQueryImage=ghcr.io/qaml-ai/camelai-selfhost-db-query@sha256:... \
ContainerEgressImage=ghcr.io/qaml-ai/camelai-selfhost-container-egress@sha256:... \
CaddyImage=ghcr.io/qaml-ai/camelai-selfhost-caddy@sha256:... \
PomeriumImage=pomerium/pomerium@sha256:... \
MainHostname=camel.example.com \
AppVanityDomain=apps.example.com \
AuthProvider=bundled-pomerium \
AuthDefaultOrgName='Example Corp' \
PomeriumAuthenticateUrl=https://authenticate.example.com \
PomeriumAuthenticateHostname=authenticate.example.com \
PomeriumIdpProvider=oidc \
PomeriumIdpProviderUrl=https://idp.example.com/application/o/camelai/ \
PomeriumIdpClientId=camelai \
PomeriumIdpClientSecretArn=arn:aws:secretsmanager:... \
SelfhostAiApiKeySecretArn=arn:aws:secretsmanager:... \
TlsMode=automatic \
Route53HostedZoneId=Z0123456789EXAMPLECloudFormation waits up to 30 minutes for bootstrap. Its wait condition receives success only after the HTTP health check and all three runtime smokes pass.
TLS and network modes
The default automatic mode gives the EC2 role narrowly scoped permission to
create and remove TXT ACME challenge records in
Route53HostedZoneId. Caddy obtains and renews the main, authenticate, and
wildcard certificates automatically. CreateRoute53Records separately
controls whether the stack creates the public A records.
Use provided mode when automated DNS credentials are not permitted. It reads
the certificate and key from Secrets Manager; both secret ARN parameters are
then required. The certificate must cover the main, authenticate,
deployed-app wildcard, and any separate iframe wildcard hostnames.
Use external TLS only behind a load balancer or identity proxy. In external
mode, port 80 is an origin port. Restrict web_ingress_cidrs or
WebIngressCidr to the upstream proxy and never expose it directly to the
internet.
When an external load balancer terminates TLS, the VM must be able to reach its public camelAI HTTPS hostname through that load balancer. camelAI uses that path to retrieve Pomerium's signing keys.
Deletion and recovery
The encrypted data volume has snapshot retention configured when the CloudFormation stack is deleted. Before deleting a production stack:
- Run and verify an application backup.
- Confirm the data-volume snapshot policy and resulting snapshot.
- Retain the AI and OIDC secrets, plus TLS secrets when using provided mode.
- Record the release manifest and exact image digests.
See Operations for application backups, restores, and verified upgrades.