Introduction
This CloudFormation template will create a stack for a full deployment pipeline for your React app with AWS CodePipeline and AWS Codedeploy.
The pipeline will react to git push performed to a branch on a Github repository and it will build the React project and upload it to an S3 static website. It will also create a custom domain name in a Route53 hosted zone, a free SSL certificate in AWS Certificate Manager and it will create a CloudFront distribution with the SSL certificate and custom domain name already configured in the distribution.
The template contains everything you need to setup this deployment pipeline:
- 2 S3 buckets (one for codepipeline artifacts and one for the static site – configured with static website hosting and the necessary bucket policy)
- An AWS CodePipeline preconfigured with everything needed to run the pipeline
- An AWS CodeDeploy build project that will build your React app (if you want just a static html site this can be skipped)
- 2 IAM Roles (one for AWS CodePipeline and one for AWS CodeBuild) configured with the necessary permissions
- A custom domain name in a Route 53 hosted zone
- An SSL certificate in Certificate Manager for your custom domain name
- A CloudFront distribution with custom domain name and SSL certificate configured
If you need to configure this without a custom domain name, without an SSL certificate and without CloudFront CDN – so just a simple S3 static website then check out this template which contains that setup.
Configuration
When you upload the template to CloudFormation and try to create the stack you will be prompted to add the following information:
- Branch. This is the branch from your github repo that you want to deploy
- Repository owner. This your github user name and the second part of the repo url (bold underlined): https://github.com/majestic-cloud/docs
- Repository. This is the project name. This is the last part of your github repo url (bold underlined): https://github.com/majestic-cloud/docs
- GithubOAuthToken. This is your github personal access token. To get this sign in to your Github account then click on your user’s icon in the top right part of the screen, then go to Settings -> Developer settings -> Personal access tokens (or you can go directly to this link: https://github.com/settings/tokens) and then click on Generate new token. After you configure the permissions (check the first checkbox named repo that gives access to all the “repo” permissions) paste the obtained token into the CloudFormation console.
- StaticSiteDomain. This is the domain name you want to use for the static website. It will get an SSL certificate automatically if your main domain is hosted in Route53.
- HostedZone. This is the name of your hosted zone. That is the main domain of your site. So if you deploy the react app to myreactapp.example.com then this is example.com and it should use Route53 for this to work.
- HostedZoneID. This is the ID of your hosted zone from Route53. Go to the Route53 console and in the list of the hosted zones you can find the ID to the right of your desired domain (which should use Route53)
Deployment
You need to go to CloudFormation, create a new stack with the template below and fill in the required fields before creating the stack.
The template
AWSTemplateFormatVersion: 2010-09-09
Parameters:
Branch:
Type: String
Default: "Type here your branch name (usually master or main)"
RepoOwner:
Type: String
Default: majestic-cloud
Repository:
Type: String
Default: myapp
GithubOAuthToken:
Type: String
Description: "Github access token"
StaticSiteDomain:
Type: String
Description: "The domain name to be used with this static site"
Default: "myreactapp.majestic-cloud.com"
HostedZone:
Type: String
Description: "The hosted zone in which we will create a subdomain"
Default: "majestic-cloud.com"
HostedZoneID:
Type: String
Description: "The ID of your hosted zone in Route53"
Default: "ZZZZZZZZZZZZZZ"
Resources:
ReactToS3CodePipeline:
Type: 'AWS::CodePipeline::Pipeline'
Properties:
RoleArn: !GetAtt CodePipeLineRole.Arn
ArtifactStore:
Location: !Ref PipelineBucket
Type: S3
Stages:
-
Name: Source
Actions:
-
Name: SourceAction
ActionTypeId:
Category: Source
Owner: ThirdParty
Provider: GitHub
Version: 1
OutputArtifacts:
-
Name: TheApp
Configuration:
Owner: !Ref RepoOwner
Repo: !Ref Repository
Branch: main
OAuthToken: !Ref GithubOAuthToken
-
Name: Build
Actions:
-
Name: BuildAction
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
InputArtifacts:
-
Name: TheApp
OutputArtifacts:
-
Name: TheBuiltApp
Configuration:
ProjectName: !Ref CodeBuild
-
Name: Deploy
Actions:
-
Name: DeployAction
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: S3
InputArtifacts:
-
Name: TheBuiltApp
RunOrder: 1
Configuration:
BucketName: !Ref DeployBucket
Extract: true
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Principal:
Service:
- "codebuild.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: /service-role/
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Action:
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketVersioning"
- "s3:PutObject"
- "s3:PutObjectAcl"
- "s3:PutObjectVersionAcl"
- "s3:DeleteObject"
Resource:
- !GetAtt PipelineBucket.Arn
- !Join ['', [!GetAtt PipelineBucket.Arn, "/*"]]
-
Effect: Allow
Action:
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketVersioning"
- "s3:PutObject"
- "s3:PutObjectAcl"
- "s3:PutObjectVersionAcl"
- "s3:DeleteObject"
Resource:
- !GetAtt DeployBucket.Arn
- !Join ['', [!GetAtt DeployBucket.Arn, "/*"]]
-
Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "cloudfront:CreateInvalidation"
Resource:
- "*"
CodePipeLineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Principal:
Service:
- "codepipeline.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Action:
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketVersioning"
- "s3:PutObject"
- "s3:PutObjectAcl"
- "s3:PutObjectVersionAcl"
- "s3:DeleteObject"
Resource:
- !GetAtt PipelineBucket.Arn
- !Join ['', [!GetAtt PipelineBucket.Arn, "/*"]]
-
Effect: Allow
Action:
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketVersioning"
- "s3:PutObject"
- "s3:PutObjectAcl"
- "s3:PutObjectVersionAcl"
- "s3:DeleteObject"
Resource:
- !GetAtt DeployBucket.Arn
- !Join ['', [!GetAtt DeployBucket.Arn, "/*"]]
-
Effect: Allow
Action:
- "codebuild:BatchGetBuilds"
- "codebuild:StartBuild"
Resource: "*"
CodeBuild:
Type: 'AWS::CodeBuild::Project'
Properties:
Name: !Sub ${AWS::StackName}-CodeBuild
ServiceRole: !GetAtt CodeBuildRole.Arn
Artifacts:
Type: CODEPIPELINE
Name: MyProject
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Type: LINUX_CONTAINER
Image: "aws/codebuild/amazonlinux2-x86_64-standard:2.0"
Source:
Type: CODEPIPELINE
BuildSpec: !Sub |
version: 0.1
phases:
pre_build:
commands:
- echo This will run npm install to install the dependencies...
- npm install
build:
commands:
- echo Actually run the build process
- npm run build
artifacts:
files:
- '**/*'
base-directory: build
PipelineBucket:
Type: 'AWS::S3::Bucket'
Properties: {}
DeletionPolicy: Retain
DeployBucket:
Type: 'AWS::S3::Bucket'
Properties:
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
DeletionPolicy: Retain
DeployBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
PolicyDocument:
Id: MyPolicy
Version: 2012-10-17
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: 's3:GetObject'
Resource: !Join
- ''
- - 'arn:aws:s3:::'
- !Ref DeployBucket
- /*
Bucket: !Ref DeployBucket
ACMCertificate:
Type: "AWS::CertificateManager::Certificate"
Properties:
DomainName: !Ref StaticSiteDomain
DomainValidationOptions:
- DomainName: !Ref StaticSiteDomain
HostedZoneId: !Ref HostedZoneID
ValidationMethod: DNS
CloudFrontDistribution:
Type: "AWS::CloudFront::Distribution"
Properties:
DistributionConfig:
Origins:
-
DomainName: !GetAtt DeployBucket.RegionalDomainName
Id: !Ref DeployBucket
S3OriginConfig:
OriginAccessIdentity: ''
DefaultRootObject: index.html
Enabled: true
Aliases:
- !Ref StaticSiteDomain
DefaultCacheBehavior:
MinTTL: 86400
MaxTTL: 31536000
ForwardedValues:
QueryString: true
TargetOriginId: !Ref DeployBucket
ViewerProtocolPolicy: "redirect-to-https"
ViewerCertificate:
AcmCertificateArn: !Ref ACMCertificate
MinimumProtocolVersion: TLSv1.1_2016
SslSupportMethod: sni-only
CustomErrorResponses:
- ErrorCode: '403'
ErrorCachingMinTTL: '300'
ResponseCode: '200'
ResponsePagePath: "/index.html"
- ErrorCode: '404'
ErrorCachingMinTTL: '300'
ResponseCode: '200'
ResponsePagePath: "/index.html"
DomainAliasForSite:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: !Join [ "", [ !Ref HostedZone, "." ] ]
Name: !Ref StaticSiteDomain
Type: A
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
Find the above template on Github: https://github.com/majestic-cloud/react-deployment-pipeline/blob/main/cfn-templates/reactToS3v2.yaml