Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.aws.template
Original file line number Diff line number Diff line change
Expand Up @@ -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=
4 changes: 3 additions & 1 deletion iac/provider-aws/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
79 changes: 62 additions & 17 deletions iac/provider-aws/domain.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,91 @@ 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
ttl = 3600
}

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
}
1 change: 1 addition & 0 deletions iac/provider-aws/init/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module "network" {

module "cloudflare" {
source = "../modules/cloudflare"
count = var.dns_provider == "cloudflare" ? 1 : 0

prefix = var.prefix
}
2 changes: 1 addition & 1 deletion iac/provider-aws/init/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ output "db_migrator_repository_name" {
// Cloudflare
// ---
output "cloudflare" {
value = module.cloudflare.cloudflare
value = var.dns_provider == "cloudflare" ? module.cloudflare : []
}

// ---
Expand Down
5 changes: 5 additions & 0 deletions iac/provider-aws/init/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ variable "region" {
variable "endpoint_ingress_subnet_ids" {
type = list(string)
}

variable "dns_provider" {
type = string
default = "cloudflare"
}
5 changes: 4 additions & 1 deletion iac/provider-aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ terraform {
}

provider "cloudflare" {
api_token = module.init.cloudflare.token
# 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"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read Cloudflare token from nested module output

When dns_provider is cloudflare (the default), this expression dereferences module.init.cloudflare[0].token, but module.init.cloudflare is now a list of module.cloudflare instances (from init/outputs.tf), where the token is nested under the cloudflare output. In that mode Terraform will fail resolving this attribute (unsupported attribute), blocking plan/apply on the default path.

Useful? React with 👍 / 👎.

}

provider "aws" {}
Expand Down Expand Up @@ -58,6 +60,7 @@ module "init" {
]

allow_force_destroy = var.allow_force_destroy
dns_provider = var.dns_provider
}

locals {
Expand Down
17 changes: 17 additions & 0 deletions iac/provider-aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
11 changes: 6 additions & 5 deletions self-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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:
Expand All @@ -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))
Expand Down
Loading