Skip to content

Instantly share code, notes, and snippets.

@sk-t3ch
Last active July 11, 2023 14:39
Show Gist options
  • Save sk-t3ch/6a2b8b5a4a6325b88cdf16398d0a2092 to your computer and use it in GitHub Desktop.
Save sk-t3ch/6a2b8b5a4a6325b88cdf16398d0a2092 to your computer and use it in GitHub Desktop.
GrowthBook Quickstart CloudFormation Template
Parameters:
Product:
Type: String
Default: growthbook
Account:
Type: String
AllowedValues:
- dev
- prod
Conditions:
IsDev: !Equals [ !Ref Account, dev ]
Resources:
DockerRepository:
Condition: IsDev
Type: AWS::ECR::Repository
Properties:
ImageTagMutability: MUTABLE
RepositoryName: !Ref Product
RepositoryPolicyText:
Version: "2008-10-17"
Statement:
Sid: ProdECR
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${ AWS::AccountId }:root"
Action:
- "ecr:BatchCheckLayerAvailability"
- "ecr:BatchGetImage"
- "ecr:GetDownloadUrlForLayer"
DocDBElasticCluster:
Type: AWS::DocDBElastic::Cluster
Properties:
AdminUserName: root
AdminUserPassword: password
AuthType: PLAIN_TEXT
ClusterName: "GrowthBookCluster"
PreferredMaintenanceWindow: "sat:04:51-sat:05:21"
ShardCapacity: 2
ShardCount: 2
SubnetIds:
- !ImportValue VPC-PrivateSubnetA
- !ImportValue VPC-PrivateSubnetB
VpcSecurityGroupIds:
- !ImportValue ClusterSecurityGroup
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${Product}-${Account}"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Outputs:
ClusterEndpoint:
Description: "Reference to the GrowthBookDocDBElasticClusterClusterEndpoint"
Export:
Name: GrowthBookDocDBElasticClusterClusterEndpoint
Value: !GetAtt DocDBElasticCluster.ClusterEndpoint
S3BucketName:
Description: "Reference to the GrowthBookS3BucketName"
Export:
Name: GrowthBookS3BucketName
Value: !Ref S3Bucket
S3BucketArn:
Description: "Reference to the GrowthBookS3BucketArn"
Export:
Name: GrowthBookS3BucketArn
Value: !GetAtt S3Bucket.Arn
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Product:
Type: String
CommitHash:
Type: String
Account:
Type: String
Resources:
AppDomainIPv4:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt LoadBalancer.DNSName
HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
HostedZoneName: !Sub
- ${Domain}.
- { Domain: !ImportValue Domain }
Name: !Sub
- growthbook.${Domain}.
- { Domain: !ImportValue Domain }
Type: A
ApiDomainIPv4:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt LoadBalancer.DNSName
HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
HostedZoneName: !Sub
- ${Domain}.
- { Domain: !ImportValue Domain }
Name: !Sub
- api-growthbook.${Domain}.
- { Domain: !ImportValue Domain }
Type: A
AppContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: AppContainerSecurityGroup
VpcId: !ImportValue VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 3000
SourceSecurityGroupId: !Ref LoadBalancerSecGroup
ApiContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ApiContainerSecurityGroup
VpcId: !ImportValue VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 3100
SourceSecurityGroupId: !Ref LoadBalancerSecGroup
LoadBalancerSecGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Load balancer only allow https
VpcId: !ImportValue VPCId
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
FromPort: 443
IpProtocol: TCP
ToPort: 443
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internal
SecurityGroups:
- !ImportValue LoadBalancerHTTPS
Subnets:
- !ImportValue PrivateSubnetA
- !ImportValue PrivateSubnetB
LoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: fixed-response
FixedResponseConfig:
MessageBody: "Endpoint not found"
StatusCode: 404
LoadBalancerArn: !Ref LoadBalancer
Port: 443
Certificates:
- CertificateArn: !ImportValue WildcardCertificate
Protocol: HTTPS
AppListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref AppTargetGroup
Type: forward
Conditions:
- Field: host-header
HostHeaderConfig:
Values:
- growthbook.domain.com
ListenerArn: !Ref LoadBalancerListener
Priority: 1
ApiListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref ApiTargetGroup
Type: forward
Conditions:
- Field: host-header
HostHeaderConfig:
Values:
- api-growthbook.domain.com
ListenerArn: !Ref LoadBalancerListener
Priority: 2
Service:
DependsOn: [ 'AppListenerRule', 'ApiListenerRule' ]
Type: AWS::ECS::Service
Properties:
Cluster: !ImportValue ECS-Cluster
DesiredCount: 1
LoadBalancers:
- ContainerName: AppContainer
ContainerPort: 3000
TargetGroupArn: !Ref AppTargetGroup
- ContainerName: AppContainer
ContainerPort: 3100
TargetGroupArn: !Ref ApiTargetGroup
DeploymentConfiguration:
MinimumHealthyPercent: 100
TaskDefinition: !Ref TaskDefinition
AppTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: 3000
Protocol: HTTP
VpcId: !ImportValue VPCId
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 2
HealthCheckPath: /
UnhealthyThresholdCount: 5
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 2
ApiTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: 3100
Protocol: HTTP
VpcId: !ImportValue VPCId
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 2
HealthCheckPath: /
UnhealthyThresholdCount: 5
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 2
TaskRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Policies:
- PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- "s3:*"
Resource:
- !Sub
- ${BucketArn}
- { BucketArn: !ImportValue GrowthBookS3BucketArn }
- !Sub
- ${BucketArn}/
- { BucketArn: !ImportValue GrowthBookS3BucketArn }
- !Sub
- ${BucketArn}/*
- { BucketArn: !ImportValue GrowthBookS3BucketArn }
PolicyName: ServiceTaskRole
ExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${Product}-ExecutionRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
TaskRoleArn: !GetAtt TaskRole.Arn
Family: Service
Memory: "768"
Cpu: "384"
ContainerDefinitions:
- Name: AppContainer
Environment:
- Name: NODE_ENV
Value: production
- Name: JWT_SECRET
Value: jwt123
- Name: ENCRYPTION_KEY
Value: encrypt123
- Name: COMMIT_HASH
Value: !Ref CommitHash
- Name: MONGODB_URI
Value: !Sub
- mongodb://root:password@${MongoUrl}:27017/growthbook?authSource=admin&tls=true&ssl=true&retryWrites=false
- { MongoUrl: !ImportValue GrowthBookDocDBElasticClusterClusterEndpoint }
- Name: API_HOST
Value: https://api-growthbook.domain.com
- Name: APP_ORIGIN
Value: https://growthbook.domain.com
- Name: UPLOAD_METHOD
Value: s3
- Name: S3_BUCKET
Value: !ImportValue GrowthBookS3BucketName
- Name: S3_REGION
Value: !Ref 'AWS::Region'
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Product}:service-${CommitHash}
Essential: true
PortMappings:
- ContainerPort: 3000
- ContainerPort: 3100
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref LogGroup
awslogs-region: !Ref 'AWS::Region'
awslogs-stream-prefix: ecs
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/ecs/${Product}
RetentionInDays: 3
AutoScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MaxCapacity: 2
MinCapacity: 1
ResourceId: !Join [ "/", [ service, !ImportValue
EngineeringECS-Cluster, !GetAtt Service.Name ] ]
RoleARN: !ImportValue ECSServiceAutoScalingRoleArn
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
AutoScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub ${Product}-service-AutoScalingPolicy
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref AutoScalingTarget
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
TargetValue: 75
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment