Skip to main content

02b · CloudFormation deploy (alternative to Terraform)

Same result as 02 · Terraform deploy — provisions the AWS resources OpenBox binds to (EKS + KMS + S3 + IRSA + optional RDS/ElastiCache/ECR). Pick this route if your organization mandates CloudFormation or you prefer a single-stack lifecycle.

Pick ONE

Do NOT mix CloudFormation and Terraform in the same account/region for OpenBox — you will drift into a two-writer conflict. Pick one, use it consistently, stick with it.

When to pick CloudFormation over Terraform

  • Your org already runs a CloudFormation-native pipeline (StackSets, ChangeSets in your CI)
  • Compliance program requires CFN drift detection (aws cloudformation detect-drift)
  • Prefer single-stack lifecycle (one cloudformation deploy command) over multi-file Terraform state
  • Team doesn't want a Terraform state backend (S3 + DynamoDB lock) — CloudFormation state lives in AWS

Otherwise, Terraform is equally valid.

Prerequisites

  • AWS credentials with PowerUserAccess (initial deploy)
  • aws CLI ≥ 2.x
  • Existing VPC + ≥ 2 private subnets across ≥ 2 AZs (bring your own)
  • Domain you control (for later TLS setup)

1. Copy the template

From the built docs site (once deployed):

curl -sSLO https://deploy-docs.openbox.ai/cloudformation/openbox-infra.yaml

Or from the repo:

cp website/static/cloudformation/openbox-infra.yaml ./openbox-infra.yaml

2. Validate

aws cloudformation validate-template \
--template-body file://openbox-infra.yaml
# Expect: template description + parameter/output metadata (no errors)
STACK_NAME=openbox-infra
VPC_ID=vpc-0xxxxxxx
SUBNET_IDS='subnet-0a...,subnet-0b...,subnet-0c...'

aws cloudformation create-change-set \
--stack-name "$STACK_NAME" \
--change-set-name "preview-$(date +%s)" \
--template-body file://openbox-infra.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=VpcId,ParameterValue="$VPC_ID" \
ParameterKey=PrivateSubnetIds,ParameterValue="\"$SUBNET_IDS\"" \
ParameterKey=ClusterName,ParameterValue=openbox-prod \
ParameterKey=EnableRDS,ParameterValue=true \
ParameterKey=EnableElastiCache,ParameterValue=true

# Wait a beat for the change set to be created, then inspect:
aws cloudformation describe-change-set \
--stack-name "$STACK_NAME" \
--change-set-name "$(aws cloudformation list-change-sets --stack-name $STACK_NAME --query 'Summaries[0].ChangeSetName' --output text)" \
--query 'Changes[].ResourceChange.{Action:Action,LogicalId:LogicalResourceId,Type:ResourceType}' \
--output table

Review the change set carefully — this is the drift protection surface CFN gives you.

4. Apply

aws cloudformation deploy \
--template-file openbox-infra.yaml \
--stack-name openbox-infra \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
VpcId="$VPC_ID" \
PrivateSubnetIds="$SUBNET_IDS" \
ClusterName=openbox-prod \
EnableRDS=true \
EnableElastiCache=true

# Expected time: 12-18 min (EKS cluster creation dominates).

5. Capture outputs

aws cloudformation describe-stacks \
--stack-name openbox-infra \
--query 'Stacks[0].Outputs' \
--output json > outputs.json

# Individual values for values-prod.yaml wiring:
CLUSTER_NAME=$(jq -r '.[] | select(.OutputKey=="ClusterName") | .OutputValue' outputs.json)
KMS_KEY_ARN=$(jq -r '.[] | select(.OutputKey=="KmsKeyArn") | .OutputValue' outputs.json)
OPA_BUCKET=$(jq -r '.[] | select(.OutputKey=="OpaBundlesBucket") | .OutputValue' outputs.json)
BACKEND_IRSA=$(jq -r '.[] | select(.OutputKey=="BackendIrsaRoleArn") | .OutputValue' outputs.json)
CORE_IRSA=$(jq -r '.[] | select(.OutputKey=="CoreIrsaRoleArn") | .OutputValue' outputs.json)
RDS_ENDPOINT=$(jq -r '.[] | select(.OutputKey=="RdsEndpoint") | .OutputValue' outputs.json)
REDIS_ENDPOINT=$(jq -r '.[] | select(.OutputKey=="ElastiCacheEndpoint") | .OutputValue' outputs.json)

echo "Cluster: $CLUSTER_NAME"
echo "KMS: $KMS_KEY_ARN"
echo "S3: $OPA_BUCKET"
echo "Backend IRSA: $BACKEND_IRSA"
echo "Core IRSA: $CORE_IRSA"
echo "RDS: $RDS_ENDPOINT"
echo "Redis: $REDIS_ENDPOINT"

6. Configure kubectl

aws eks update-kubeconfig --name "$CLUSTER_NAME"
kubectl get nodes # expect 2-3 nodes Ready

7. Migrate database schema (only if EnableRDS=true)

Chart's schema migration Job runs on helm install — no manual DB step needed.

Next

Same as Terraform path: 03 · Helm install →

Drift detection

CloudFormation's headline advantage over Terraform for compliance-heavy orgs:

aws cloudformation detect-stack-drift --stack-name openbox-infra
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id <id-from-previous-call>

# Then get details of drifted resources:
aws cloudformation describe-stack-resource-drifts \
--stack-name openbox-infra \
--stack-resource-drift-status-filters MODIFIED DELETED

Run periodically (or from CI) to catch console-only changes that break IaC.

Update

Edit openbox-infra.yaml, then:

# Preview first
aws cloudformation create-change-set \
--stack-name openbox-infra \
--change-set-name "update-$(date +%s)" \
--template-body file://openbox-infra.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameters ... # same as create

# Apply
aws cloudformation deploy \
--template-file openbox-infra.yaml \
--stack-name openbox-infra \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides ...

Rollback

CloudFormation auto-rolls-back on failed deploy (unless --no-execute-changeset was used). Manual rollback:

# List revisions:
aws cloudformation list-stack-events --stack-name openbox-infra --max-items 20

# Full teardown (destructive):
aws cloudformation delete-stack --stack-name openbox-infra
aws cloudformation wait stack-delete-complete --stack-name openbox-infra

Retained resources (won't be deleted even by delete-stack):

  • OpaBundlesBucketDeletionPolicy: Retain (empty + delete manually if needed)
  • RdsInstanceDeletionPolicy: Snapshot (final snapshot taken automatically)

Troubleshooting

InsufficientCapabilitiesException — you forgot --capabilities CAPABILITY_NAMED_IAM. IRSA roles + RoleName fields need this flag.

Template format error on IRSA condition — likely a YAML indentation issue in the Fn::Sub / Fn::Select chain. aws cloudformation validate-template catches most of these; run before deploy.

Stack stuck in UPDATE_ROLLBACK_FAILED — one or more resources can't rollback (usually RDS or the OIDC provider). Use continue-update-rollback --resources-to-skip:

aws cloudformation continue-update-rollback \
--stack-name openbox-infra \
--resources-to-skip <LogicalResourceId>

Common failure modes are the same as Terraform — see S2 troubleshooting →.