Building a Static Website in S3 with AWS Code Pipeline
Tags: aws, awscommunitybuilders, s3
Published: 2022-08-15 14:23:00

## Introduction 
This is a write-up of a talk I did on the same topic for PWA Pilipinas and AWS Siklab Pilipinas last March 20, 2022. I talked about a few basic AWS services to build up the most common architecture of a static website namely: S3, CloudFront, Route53, ACM, and IAM.
{% embed https://www.youtube.com/watch?v=ajS_W6k8MY4 %}
...aaaand, just in case you're still not aware: AWS launched a role-playing game called [Cloud Quest](https://explore.skillbuilder.aws) in the skillbuilder website where building a static website is the first task!

## What is a Static Website?
**Static Websites** are nothing but a collection of lightweight static files (these can be html, css, javascript, image files), and these are served by a host to the web browser or the client accessing the site, _exactly_ as they stored.  Regardless if **user1** tries to access the homepage of the site (index.html), and **user2** tries to access the same, they will get exactly the same content. It’s static (does not change with conditions) as opposed to a dynamic website which can differ from user to user.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nyxitp4tts3hpsx1iqhm.png)
Basic design of a static website is you have client browsers (it can either be from a computer, or mobile) accessing a domain (www.example.com) and behind this domain, it points to a webserver that has the static files being requested by the client. So it’s served via HTTP/HTTPS.
In a cloud architecture, where things can be **serverless** and the user has no knowledge of the underlying server infrastructure, it would look something like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dym0v51k2668za6dor2r.png)
1. **Route53** is the DNS service of AWS and is extremely reliable and cost effective way to route end users to internet apps. It can translate names such as www.example.com to  the numeric IP such as 202.54.44.181 that computers use to talk to each other.
2. **Cloudfront** on the one hand is the _content delivery network_ (CDN) service of AWS. It provides caching capabilities to improve performance. It stores frequently accessed files in the edge location (which is closer to the end user) so the content doesn’t have to be retrieved from the backend repeatedly.
3. **AWS Certificate Manager (ACM)** is a service that lets you easily provision, manage, and deploy public and private Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates for use with AWS services and your internal connected resources.
4. We also have **S3**. It’s an object storage service (it’s _AWS managed_ meaning you don’t own the server maintenance). It provides high availability, scalability, security and performance.
In this architecture, we'll deploy a React application which is popular framework for making dynamic web applications, but on this demo, we will execute the build process to generate _static files_ for deployment.  

## Building the Infrastructure Step-by-Step
**Pre-Requisites**
1. Node
2. Git
3. AWS-CLI 

**Create the React App**
1. Run the following commands to create a working directory and create a boilerplate react application
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/il20hxyl2szp9z8xo71z.png)
Once done, it will show up something like below:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/evwpnw5ya7tco37qckeb.png)
2. Start the application locally
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jn4fpiwk9oj5dx44kz1g.png)
It will load up in a browser in your localhost
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv0vbyok3h7o9xzhq66k.png)
Now you know it works!
3. To create the static files to deploy, run the following command
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhd15k215xr5r5mpamti.png)
You should then see a build folder containing the static files
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rejuvpwau2pxjcurgefw.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/32jp01fnn59ovkrenf0a.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5rhf48djhxuzv06z4qt.png)
**Setup the DNS**
1. In AWS Management Console, go to the Route53 service, and look for **Register domain**.  
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5l0n15q6oydc7xxiyfm8.png)
**.com** are of course the ones that most sites use and looks legitimate, so if it's for a company/institution, you should always go for a .com.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahvbrulhx1frvq3hl4m2.png)
2. Pay a minimal fee for privacy protection (this is only needed for .com websites)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zef7a1uw71b3bct9aodw.png)
3. Complete your order
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ajpszmb93yo1vjs8600.png)
When you register a domain with Amazon Route 53 or you transfer domain registration to Route 53, AWS configure the domain to renew automatically. The automatic renewal period is typically one year, although the registries for some top-level domains (TLDs) have longer renewal periods.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3sr81si9abf8piszacpc.png)
Your domain should show up under **Pending requests** first, then move to **Registered domains** once complete. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nnb7ej8zsl16u1ott60.png)
AWS will send you a confirmation email
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4yziuq1uqegomfav2vv.png)
...as well as an email validation
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hs9j2ag1qn6jufz89bi.png)
Verification link will create a hosted zone. A hosted zone is an Amazon Route 53 concept. A **hosted zone** is analogous to a traditional DNS zone file; it represents a collection of records that can be managed together, belonging to a single parent domain name. All resource record sets within a hosted zone must have the hosted zone's domain name as a suffix.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wp97hhhv773tlsuvvrin.png)
Verify will send another email and that completes the Domain setup!       
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dftra5dy3l3h7elqj4f.png)
It is not pointing to anything yet, so it will not load anything when you browse it
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/12sw62rahjf9wd6rxaks.png)
**Create an S3 Bucket**
1. In the AWS management console, go to S3
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2tf2ecxtgha1il924zjy.png)
Create two buckets namely: **www.girlwhocodes.click** and **girlwhocodes.click**.    
Create the first onw without www, and keep everything as default
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0cwmi09j71szxnz4ko7.png)
Once created, do the same steps for www. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8i7ypbd8eayy9iz5igc.png)
We should now have two buckets:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8nvwpq982sy659mnjwr.png)
**Create an IAM user for AWS-CLI**
We need to create an IAM user since the files will be uploaded using AWS-CLI. There's also an option to upload the files directly to S3 via the AWS management console, but this will demo the AWS-CLI option.
1. Go to IAM service
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dytr9erzc51kxyt61zp4.png)
Follow the wizard to create a user
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74mw7zxft9teshuda5hq.png)
Attach an AdministratorAccess to keep it simple
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9h2bt0e16x1bdgel1p7o.png)
Tags are optional
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8264yta1m2ixr122zdjj.png)
Complete the wizard...
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdxxa7jol3x8kfsjbyhx.png) 
2. Set up the user in your local machine by configuring the AWS Access Key ID and AWS Secret Access Key of your user
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fxxols7m3ljg6jcdpgc4.png)
3. Test out the access by running any AWS-CLI command
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/klhw384xowmgt1j3x1v3.png)
**Upload the Static Files to S3**  
1. Run the s3 sync command to copy your local React build files to the s3 bucket
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1q681uyswe1dfr6btjev.png)
2. You can then verify in the AWS management console if the files were uploaded
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/txgck4ut9iqsxh98h2it.png)
**Update the S3 bucket permissions and Enable for Static Website Hosting**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33i08sfikvi9llfcouoe.png)
By default, AWS blocks public access to your S3 bucket. AWS recommends that you block all public access to your buckets.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apno28u19w075cfszswb.png)
1. For simplicity, we'll keep the s3 bucket accessible. The update will prompt for a confirmation that this is what you want to do.   
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k25ondv8quik91w5et0s.png)
Tada!
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ki0jtm68z7c1yzo0it1h.png)
2. To make the objects in your bucket publicly readable, write a bucket policy that grants everyone s3:GetObject permission.   
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xcbln26kihwgnmn36dd.png)
3. Enable the bucket for static website hosting (choose Enable)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ctbk65ttpf0oqusfgdfz.png)
In index document, enter the filename of the home page, typically index.html.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k25gh4kxt6peghhoh235.png)
Note: For non-wwww, since we don't want to maintain 2 copies of the files, select **Redirect requests for an object** 
4. To quickly test this, click on the index.html file in your bucket. In the properties section, you can see an object endpoint. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wuhr5qzcqczzq1ezzq83.png)
The **endpoint** is the Amazon S3 website endpoint for your bucket object. Clicking on the endpoint will load up the page in a browser.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sb8jpoibvvhuemzyjbm1.png)
**More Domain Configurations!** 
1. Add an A record to S3. Click on hosted zone:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u93o97sw5246j6t4kpjk.png)
By default, you should have an NS and an SOA record.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fwoq9ddj87zuwsfol17j.png)
An NS (name server) record indicates which DNS server is authoritative for that domain (i.e. which server contains the actual DNS records). Basically, NS records tell the Internet where to go to find out a domain's IP address.
An SOA (start of authority) record stores important information about a domain or zone such as the email address of the administrator, when the domain was last updated, and how long the server should wait between refreshes. All DNS zones need an SOA record in order to conform to IETF standards.
Proceed to create the www "A record" and route this to S3.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j8x1devoug6izqmclaza.png)
2. Add another "A record" for the non-www domain
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dzfc5zisyf2qj51iejbn.png)
Once done, you should now have the following records
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i59uk8kfyk5twqhk4saj.png)
Since your domain now points to S3, it should already load once viewed from a browser
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hbgqht0tykq2wlvduj3.png)
You should note that this website is served from AmazonS3 when you view the response headers for Server.     
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/23w01v8rlf9xnvzosbuc.png)
Since this website is unsecure, we also need to setup CloudFront and attach a TLS certificate.  
**Request a Public Certificate in ACM**
1.  Go to the ACM service. Make sure to request or import the certificate in the US East (N.Virginia) region.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4tremkpr3nuqlismeb8.png)
2. Follow the wizard to request a public certificate
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87qaj9ictgh2stlr4f2u.png)
Add both www and non-www in the fully qualified domain names
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nsvngn80vltxifkj80nm.png)
3. Choose DNS validation as the validation method because it is easier. Tags can be skipped, then click on Request.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgfsuljplkojtiys53gf.png)
It will bring you to this page showing **pending validation**. Click on create records.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0kpeu05f4ub6lte7az1p.png)
The **canonical name (CNAME)** record is used in lieu of an A record, when a domain or subdomain is an alias of another domain. A CNAME Record is used in the Domain Name System (DNS) to create an alias from one domain name to another domain name. 
4. From ACM, you can simply click on **Create records in Route53** to automatically add the CNAME records without having to copy and paste them there.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3a9w35uyvy937h83l093.png)
If you open Route53, those records should have been added.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nh40v43zceit2evryg8y.png)
In ACM, the certificate state should change from **Pending Validation** to **Issued**.
**Setup CloudFront**
1. Go to AWS Console and click on CloudFront
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttdmu0f9ipzgwuys5akk.png)
2. Add the details in the wizard. Origin in the source of the distribution, so add the S3 path there.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/85kvx1fp0an9ve0tw3ie.png)
Note that we need two distributions (one for www and one for non-www). 
3. Add the CNAME record
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbvnfgyr3vw2gw5z9ic8.png)
4. In the same wizard, add the ACM certificate
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dhotfapwhfqi85qa2z1.png)
Also, select redirect http to https. Everything else should be default so click on **Create distribution**.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q95uub2lnu0jugxuivcs.png)
Do the same steps for the non-www domain
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vogg4qbrjesdtuyw04ka.png)
If you reload the page in a browser, you'll see that content is being served by S3, but there's a response header saying it's now via a CloudFront distribution.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dpchsfkszlli3e8h367n.png)
5. Update the Route53 www and non-www records to point now to CloudFront
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/td41mzab3ey3dkdq7tsn.png)
When you reload the page, it will now show as secure
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm5o9cbzvv0hwgq5244i.png)
If you do any file changes in S3 and it doesn't load in the browser, it's because CloudFront is service cached content. Invalidate it to force it to pull again from S3.  
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ca8n0h0adanlvelg67ha.png)

## Create a Deployment Pipeline (Extra!)
As a DevOps Engineer, I have to discuss more about pipelines so we'll explore more about Code Pipeline which is another AWS service.
1. Create a pipeline in Code Pipeline. Go through the wizard mostly with defaults. Also create a new service role.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qznvfemqke2mfuzhdexr.png)
Select the following options
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx6bg79136ojn3f26lyl.png)
2. For the source stage, since I've setup my code in GitHub, I will set it as my source
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyf1v77wvmeixpjqyoiq.png)
3. We also need to setup a connection to GitHub 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/130ixrbkot837ckn0225.png)
Select GitHub here
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9c8z0yc0spcatpyr7k0e.png)
Add a name to the connection and click on Connect
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtbt20fdfork6ie8cacs.png)
There should be a one-time setup for the verification of the connection
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3glv56besnha6dq5vgn5.png)
Select the repositories that you want to allow AWS access to
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqme1h1u3j2sw9snlfjq.png)
It should set up a code for you, then click on Connect to complete the wizard
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/buw614zggxrpsrzz98dg.png)
4. Proceed to the other steps of the wizard
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xebnr1hgqm75bcncrqji.png)
Note: I skipped the build stage for now since I am building the files locally.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ft2cn22pwgn2ilwjd571.png)
5. In the deploy stage, select Amazon S3 as the provider
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mghr9ek5jee3bp1yrx5g.png)
6. Review the pipeline in the final step
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bqikeflk8ex45dulst22.png)
Then create!
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h92w46y1eldrk3z6u1ge.png)
The pipeline will be created, but will show up with red marks
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7t6dcxsy05dg3j1kx9ju.png)
If you click the error, it shows the reason that the S3 bucket does not allow ACLs yet
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6bakaqzvdro9pxgv2lc0.png)
This can be edited in S3 via the Management Console
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vi0mk0y026lwryg8fzmm.png)
...and clear up the red marks in the pipeline
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iodwu6gv2gdk5jpnjblg.png)

## Test the Pipeline
1. Update the files of your static website with something as simple as updating the paragraph  
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qd4ou8ibwpd9v07fk3eo.png)
2. Test the change locally
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzgl19mit6ai8a55p7p2.png)
3. Run `npm build` to create new static files
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw1trl922d40m8g5zgj1.png)
4. Push the code in GitHub
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j63x67mricm12e2442v3.png)
5. The pipeline should get triggered with the push action
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72z1t0rkbw9tvqp14gy2.png)
6. The files will be uploaded to S3
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4navpqutvh02ca6otzmk.png)
7. Invalidate the distribution cache again to see the changes reflect
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xhef2n3cu1mt5bbi7r6.png)
This now completes the static website architecture with a pipeline for continuous deployment!
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8z6sahhy43fwwmb2cdav.png)

There is a [Part 2](https://dev.to/aws-builders/basics-of-hosting-a-static-website-in-aws-3ko0) of this article that I wrote where the manual process of the architecture build-up is done with Terraform Code. Follow the link if you want to check that out!