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.
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 deploycommand) 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) awsCLI ≥ 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)
3. Preview via ChangeSet (recommended)
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):
OpaBundlesBucket—DeletionPolicy: Retain(empty + delete manually if needed)RdsInstance—DeletionPolicy: 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 →.