From 31e4eadbc1c7a616b79de6c12f7ddf63478e5c93 Mon Sep 17 00:00:00 2001 From: Nofar Sinder Date: Tue, 14 Apr 2026 18:48:39 +0300 Subject: [PATCH 1/2] feat(aws): add Route53 as alternative DNS provider to Cloudflare Allow self-hosters to use AWS Route53 instead of Cloudflare for DNS management by introducing a `dns_provider` variable. This removes Cloudflare as a hard dependency for AWS deployments. Set DNS_PROVIDER=route53 and ROUTE53_ZONE_ID in your .env file to use it. Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.aws.template | 6 +++ iac/provider-aws/Makefile | 4 +- iac/provider-aws/domain.tf | 79 +++++++++++++++++++++++------- iac/provider-aws/init/main.tf | 1 + iac/provider-aws/init/outputs.tf | 2 +- iac/provider-aws/init/variables.tf | 5 ++ iac/provider-aws/main.tf | 3 +- iac/provider-aws/variables.tf | 17 +++++++ self-host.md | 11 +++-- 9 files changed, 103 insertions(+), 25 deletions(-) diff --git a/.env.aws.template b/.env.aws.template index fc8bcb80bd..6e8e295182 100644 --- a/.env.aws.template +++ b/.env.aws.template @@ -18,3 +18,9 @@ PREFIX=e2b- # prod, staging, dev TERRAFORM_ENVIRONMENT=dev + +# DNS provider: cloudflare (default) or route53 +# DNS_PROVIDER=cloudflare + +# Required when DNS_PROVIDER=route53: your Route53 hosted zone ID +# ROUTE53_ZONE_ID= diff --git a/iac/provider-aws/Makefile b/iac/provider-aws/Makefile index 8ea6069066..5db883495f 100644 --- a/iac/provider-aws/Makefile +++ b/iac/provider-aws/Makefile @@ -45,7 +45,9 @@ tf_vars := AWS_PROFILE=$(AWS_PROFILE) AWS_REGION=$(AWS_REGION) \ $(call tfvar, DB_MAX_OPEN_CONNECTIONS) \ $(call tfvar, DB_MIN_IDLE_CONNECTIONS) \ $(call tfvar, AUTH_DB_MAX_OPEN_CONNECTIONS) \ - $(call tfvar, AUTH_DB_MIN_IDLE_CONNECTIONS) + $(call tfvar, AUTH_DB_MIN_IDLE_CONNECTIONS) \ + $(call tfvar, DNS_PROVIDER) \ + $(call tfvar, ROUTE53_ZONE_ID) .PHONY: provider-login provider-login: diff --git a/iac/provider-aws/domain.tf b/iac/provider-aws/domain.tf index 277f3b55c2..7b853213dc 100644 --- a/iac/provider-aws/domain.tf +++ b/iac/provider-aws/domain.tf @@ -4,35 +4,28 @@ locals { // Take last 2 parts (1 dot) domain_root = local.domain_is_subdomain ? join(".", slice(local.domain_parts, length(local.domain_parts) - 2, length(local.domain_parts))) : var.domain_name -} -data "cloudflare_zone" "domain" { - name = local.domain_root + use_cloudflare = var.dns_provider == "cloudflare" + use_route53 = var.dns_provider == "route53" } -resource "aws_acm_certificate" "wildcard" { - domain_name = "*.${var.domain_name}" - validation_method = "DNS" - - lifecycle { - create_before_destroy = true - } -} +// --- Cloudflare --- -resource "aws_acm_certificate_validation" "wildcard" { - certificate_arn = aws_acm_certificate.wildcard.arn +data "cloudflare_zone" "domain" { + count = local.use_cloudflare ? 1 : 0 + name = local.domain_root } resource "cloudflare_record" "cert" { - for_each = { + for_each = local.use_cloudflare ? { for dvo in aws_acm_certificate.wildcard.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name value = dvo.resource_record_value type = dvo.resource_record_type } - } + } : {} - zone_id = data.cloudflare_zone.domain.zone_id + zone_id = data.cloudflare_zone.domain[0].zone_id name = each.value.name type = each.value.type value = each.value.value @@ -40,10 +33,62 @@ resource "cloudflare_record" "cert" { } resource "cloudflare_record" "routing" { - zone_id = data.cloudflare_zone.domain.zone_id + count = local.use_cloudflare ? 1 : 0 + zone_id = data.cloudflare_zone.domain[0].zone_id name = "*.${var.domain_name}" type = "CNAME" value = aws_lb.ingress.dns_name ttl = 3600 proxied = false } + +// --- Route53 --- + +data "aws_route53_zone" "domain" { + count = local.use_route53 ? 1 : 0 + zone_id = var.route53_zone_id +} + +resource "aws_route53_record" "cert" { + for_each = local.use_route53 ? { + for dvo in aws_acm_certificate.wildcard.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + record = dvo.resource_record_value + type = dvo.resource_record_type + } + } : {} + + zone_id = data.aws_route53_zone.domain[0].zone_id + name = each.value.name + type = each.value.type + ttl = 300 + + records = [each.value.record] +} + +resource "aws_route53_record" "routing" { + count = local.use_route53 ? 1 : 0 + zone_id = data.aws_route53_zone.domain[0].zone_id + name = "*.${var.domain_name}" + type = "CNAME" + ttl = 300 + + records = [aws_lb.ingress.dns_name] +} + +// --- ACM Certificate (shared) --- + +resource "aws_acm_certificate" "wildcard" { + domain_name = "*.${var.domain_name}" + validation_method = "DNS" + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_acm_certificate_validation" "wildcard" { + certificate_arn = aws_acm_certificate.wildcard.arn + + validation_record_fqdns = local.use_route53 ? [for record in aws_route53_record.cert : record.fqdn] : null +} diff --git a/iac/provider-aws/init/main.tf b/iac/provider-aws/init/main.tf index 54bf674c07..693e51a342 100644 --- a/iac/provider-aws/init/main.tf +++ b/iac/provider-aws/init/main.tf @@ -12,6 +12,7 @@ module "network" { module "cloudflare" { source = "../modules/cloudflare" + count = var.dns_provider == "cloudflare" ? 1 : 0 prefix = var.prefix } diff --git a/iac/provider-aws/init/outputs.tf b/iac/provider-aws/init/outputs.tf index 3e2215b6a5..2b895d6b97 100644 --- a/iac/provider-aws/init/outputs.tf +++ b/iac/provider-aws/init/outputs.tf @@ -68,7 +68,7 @@ output "db_migrator_repository_name" { // Cloudflare // --- output "cloudflare" { - value = module.cloudflare.cloudflare + value = var.dns_provider == "cloudflare" ? module.cloudflare : [] } // --- diff --git a/iac/provider-aws/init/variables.tf b/iac/provider-aws/init/variables.tf index 1909902194..21ff8c074a 100644 --- a/iac/provider-aws/init/variables.tf +++ b/iac/provider-aws/init/variables.tf @@ -17,3 +17,8 @@ variable "region" { variable "endpoint_ingress_subnet_ids" { type = list(string) } + +variable "dns_provider" { + type = string + default = "cloudflare" +} diff --git a/iac/provider-aws/main.tf b/iac/provider-aws/main.tf index 2b04180f6e..e801acf687 100644 --- a/iac/provider-aws/main.tf +++ b/iac/provider-aws/main.tf @@ -29,7 +29,7 @@ terraform { } provider "cloudflare" { - api_token = module.init.cloudflare.token + api_token = var.dns_provider == "cloudflare" ? module.init.cloudflare[0].token : "unused" } provider "aws" {} @@ -58,6 +58,7 @@ module "init" { ] allow_force_destroy = var.allow_force_destroy + dns_provider = var.dns_provider } locals { diff --git a/iac/provider-aws/variables.tf b/iac/provider-aws/variables.tf index 404d2dfa92..5b6c9db7f1 100644 --- a/iac/provider-aws/variables.tf +++ b/iac/provider-aws/variables.tf @@ -2,6 +2,23 @@ variable "domain_name" { type = string } +variable "dns_provider" { + type = string + description = "DNS provider to use for domain management. Supported values: cloudflare, route53" + default = "cloudflare" + + validation { + condition = contains(["cloudflare", "route53"], var.dns_provider) + error_message = "dns_provider must be one of: cloudflare, route53" + } +} + +variable "route53_zone_id" { + type = string + description = "Route53 hosted zone ID. Required when dns_provider is route53" + default = "" +} + variable "allow_force_destroy" { default = false } diff --git a/self-host.md b/self-host.md index 74a7b93ece..8503c2011b 100644 --- a/self-host.md +++ b/self-host.md @@ -26,8 +26,7 @@ **Accounts** -- Cloudflare account -- Domain on Cloudflare +- DNS provider: Cloudflare account with domain, **or** AWS Route53 hosted zone - PostgreSQL database (Supabase's DB only supported for now) **Optional** @@ -121,8 +120,10 @@ Now, you should see the right quota options in `All Quotas` and be able to reque - `AWS_ACCOUNT_ID` - your AWS account ID - `AWS_REGION` - the AWS region to deploy to (must support bare metal instances for Firecracker) - `PREFIX` - name prefix for all resources (e.g. `e2b-`) - - `DOMAIN_NAME` - your domain managed by Cloudflare + - `DOMAIN_NAME` - your domain - `TERRAFORM_ENVIRONMENT` - one of `prod`, `staging`, `dev` + - `DNS_PROVIDER` - `cloudflare` (default) or `route53` + - `ROUTE53_ZONE_ID` - your Route53 hosted zone ID (required when `DNS_PROVIDER=route53`) 2. Run `make set-env ENV={prod,staging,dev}` to start using your env 3. Run `make provider-login` to authenticate with AWS ECR 4. Run `make init`. This creates: @@ -131,9 +132,9 @@ Now, you should see the right quota options in `All Quotas` and be able to reque - ECR repositories for container images - S3 buckets for templates, kernels, builds, and backups - Secrets in AWS Secrets Manager (with placeholder values) - - Cloudflare DNS records and TLS certificates + - DNS records and TLS certificates (via Cloudflare or Route53) 5. Update the following secrets in [AWS Secrets Manager](https://console.aws.amazon.com/secretsmanager) with actual values: - - `{prefix}cloudflare` - JSON with `TOKEN` key + - `{prefix}cloudflare` - JSON with `TOKEN` key (**only when using Cloudflare**) > Get Cloudflare API Token: go to the [Cloudflare dashboard](https://dash.cloudflare.com/) -> Manage Account -> Account API Tokens -> Create Token -> Edit Zone DNS -> in "Zone Resources" select your domain and generate the token - `{prefix}postgres-connection-string` - your PostgreSQL connection string (**required**) - `{prefix}supabase-jwt-secrets` - Supabase JWT secret (optional / required for the [E2B dashboard](https://github.com/e2b-dev/dashboard)) From 5608007c3c24fa886ac4a9837a460194aa378343 Mon Sep 17 00:00:00 2001 From: Nofar Sinder Date: Tue, 14 Apr 2026 20:39:12 +0300 Subject: [PATCH 2/2] fix(aws): use valid dummy token for cloudflare provider when using route53 Terraform validates the Cloudflare provider config even when no Cloudflare resources are created. Use a 40-char dummy token to pass validation. --- iac/provider-aws/main.tf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iac/provider-aws/main.tf b/iac/provider-aws/main.tf index e801acf687..e7be59d5da 100644 --- a/iac/provider-aws/main.tf +++ b/iac/provider-aws/main.tf @@ -29,7 +29,9 @@ terraform { } provider "cloudflare" { - api_token = var.dns_provider == "cloudflare" ? module.init.cloudflare[0].token : "unused" + # When not using Cloudflare, provide a syntactically valid dummy token. + # No Cloudflare resources are created (all have count=0), but Terraform still validates the provider config. + api_token = var.dns_provider == "cloudflare" ? module.init.cloudflare[0].token : "0000000000000000000000000000000000000000" } provider "aws" {}