Auto-deploy SPA with AWS S3 and CloudFront using GitLab CI/CD
In this blog, We will see how to host a single page website by storing files on AWS S3, serve them using CloudFront with GitLab CI/CD.
Single Page Application(SPA)
Single Page Application is a web application or website that interacts with the user without making a request to the server to fetch new HTML. There are many ways we can create SPA in many languages but we will use nodejs docker image for demo.
Setup S3 Bucket
Amazon S3 has a simple web services interface that we can use to store and retrieve any amount of data, at any time, from anywhere on the web.
- Create an S3 bucket named exactly after the domain name, for example
website.com
then go into the bucket. - Select Properties tab, click the Static Website section.
- Select
Use this bucket to host a website
and type index.html into Index Document field. - Don't enter anything else in other fields.
- You can see on top, it is showing
Endpoint :
http://website.com.s3-website-us-east-1.amazonaws.com. - Click on
Save
button.
- Select
- Then select on Permissions tab, go to
Bucket Policy
. Copy below policy and paste into editor:
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowPublicRead",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/*"
}
]
}
Be sure to replace BUCKET_NAME with yours.
- Click
Save
, it will ask you to confirmThis bucket has public access
, click Save again. - Now upload
index.html
into bucket and you will be able to see it on bucketenpoint
url.
Setup CloudFront
Amazon CloudFront is a web service that speeds up distribution of our static and dynamic web content, such as .html, .css, .js, and image files, to our users.
- Go to the CloudFront from Services, click on
Create Distribution
and selectGet Started
for Web. - In
Origin Domain Name
, paste the "endpoint" previously created in S3 (without http://). - Select
yes
for Compress Objects Automatically. - In Alternate Domain Names (CNAMEs), enter the domain name.
- In Default Root Object, enter
index.html
. - Leave everything else as it is.
- Click on
Create Distribution
. - We can see, our distribution status is "In Progress", it will take few minutes to get
Deployed
. - The distribution will have a domain name like
d15mue0rz621ef.cloudfront.net
. - Set
A record
forYOUR DOMAIN
with abovedistribution domain name
. - Now go to our distribution, select
Error Pages
. - We need to make sure all requests to the server (S3 in this case) return something even if no file exists, click on
Create Custom Error Response
.- HTTP Error Code: 404 Not Found
- TTL: 0
- Custom Error Response: Yes
- Response Page Path: /index.html
- HTTP Response Code: 404/200 (if have handled it on frontend, enter 404 else 200)
Setup GitLab CI/CD
GitLab offers a continuous integration service. If you add a .gitlab-ci.yml
file to the root directory of your repository, then each commit or push, triggers your CI pipeline. The pipeline will generate our website files and then we will use aws cli to host our website. This is called CD(continuous deployment)
. GitLab provides it's own CI/CD pipeline. We will use it to automate our work.
-
Open your GitLab repository, go to Settings -> CI/CD from the left side menu.
-
Here we will enter our AWS credentials so that our automation script can use it.
-
Expand variables section and enter following variables and their values.
- AWS_ACCESS_KEY_ID: YOUR-AWS-ACCESS-KEY-ID
- AWS_SECRET_ACCESS_KEY: YOUR-AWS-SECRET-ACCESS-KEY
- S3_BUCKET_NAME: YOUR-S3-BUCKET-NAME
- DISTRIBUTION_ID: CLOUDFRONT-DISTRIBUTION-ID
-
Click on
Save variables
. -
create new file
.gitlab-ci.yml
and copy below script into the file.
image: docker:latest
stages:
- build
- deploy
build:
stage: build
image: node:8.11.3
script:
- export API_URL="https://api.logicwind.com/" //any evnvironment variables your code requires
- npm install
- npm run build
- echo "BUILD SUCCESSFULLY"
artifacts:
paths:
- dist/
expire_in: 20 mins
environment:
name: production
only:
- master
deploy:
stage: deploy
image: python:3.5
dependencies:
- build
script:
- export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID // same variable we declared in gitlab CI settings
- export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
- export S3_BUCKET_NAME=$S3_BUCKET_NAME
- export DISTRIBUTION_ID=$DISTRIBUTION_ID
- pip install awscli --upgrade --user
- export PATH=~/.local/bin:$PATH
- aws s3 sync --acl public-read --delete dist $S3_BUCKET_NAME
- aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths '/*'
- echo "DEPLOYED SUCCESSFULLY"
environment:
name: production
only:
- master
build stage
Script will run build command and generate single page application source like html, css, js files under dist folder. Most important part is artifact
part, we are telling our CI runner to keep /dist
folder for next 20 mins, then it will deploy our artifact. basically our distribution code. we can download, test and verify our code whether is it exactly what we wanted or not.
deploy stage
we will use python interface and export aws credentials for authentication so that we can put our code on S3 bucket. The script will install awscli using pip and it will initialize it with our provided configs.
- Next it will sync our provided folder with given bucket and delete, upload necessary files, folders with public read access.
- Next, our script will run invalidate command on CloudFront to invalidate our existing files for given path, here
/*
because we want our all files to get indexed again. - Once we see
DEPLOYED SUCCESSFULLY
andJob succeeded
on the console, we can say that we have successfully deployed our SPA on S3 and serving it using AWS CloudFront. - Next time we just have to push our changes on GitLab and all this happens in the background automatically and we will be able to see our changes live in just a few minutes.
Help links:
- AWS S3 - https://aws.amazon.com/s3/
- AWS CloudFront - https://aws.amazon.com/cloudfront/
- GitLab CI/CD - https://about.gitlab.com/features/gitlab-ci-cd/
- GitHub Gist - https://gist.github.com/bradwestfall/b5b0e450015dbc9b4e56e5f398df48ff