How to Seamlessly Host Your Personal Portfolio Website on AWS with S3, CloudFront, Route 53, and GitHub Actions
Discover the step-by-step process of hosting your personal portfolio website on AWS using S3, CloudFront, Route 53, and GitHub Actions. This guide will help you make your portfolio internet-facing and automate updates and deployments with continuous integration and continuous delivery (CI/CD). Perfect for developers looking to streamline their website hosting with AWS services.
- A public hosted zone is a container that holds information about how you want to route traffic on the internet for a specific domain, such as example.com, and its subdomains (acme.example.com, zenith.example.com).
- Navigate to the Route 53 service in the AWS Console. In the left-hand panel, locate and click on "Registered Domains," then select "Register Domain."
- Consider creating a domain using your first and last name, such as "elliotalderson.com." These domains typically cost around $15 per year. After completing the registration process and receiving approval, you will see your new domain listed under the "Hosted Zones" section in the left-hand panel. This hosted zone is where you will manage the DNS settings for your domain.
- We will come back to Route53 in a later step for routing traffic to Cloudfront.
- AWS CDK Hosted Zone Code Snippet:
1
2
3
4
5
6
7
8
9
10
// Route53 Hosted Zone Lookup for Site Hosting and Record Adding
let hostedZone: route53.IHostedZone;
const domainName = "elliotalderson.com";
hostedZone = route53.HostedZone.fromLookup(
this,
"ElliotAldersonHostedZone",
{
domainName: domainName,
},
);
- There are various approaches to building your portfolio, commonly involving files such as HTML, CSS, JavaScript, and images. Personally, I use React and webpack, running
npm run build
to generate a build directory containing my website assets. However, for simplicity, this tutorial can work with just anindex.html
file that contains "Hello World".
- Create a general-purpose S3 bucket with ACLs disabled. Be sure to uncheck all public access settings as we want our S3 bucket to be public and internet-facing. You can enable bucket versioning, set the encryption type to Server-side encryption with Amazon S3 managed keys (SSE-S3), and disable the bucket key and object lock.
- After creating the bucket, enable static website hosting at the very bottom of the properties tab in the bucket. Be sure to specify your index document, typically named
index.html
unless you named it something else or placed it inside a directory. - Once that's configured, you can upload your website assets to your S3 bucket using the uploader. Notice how my
index.html
is in the root directory. - Add the following Bucket Policy in the permissions tab to your bucket. Be sure to replace BUCKET_NAME
1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicReadAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/*"
}
]
}
- You can validate that you completed this step correctly by navigating to your bucket website endpoint, which is displayed in the static website hosting section in the properties tab. Your website should be displayed there.
- AWS CDK S3 Bucket Code Snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// S3 Bucket for Website Hosting
const grantStarkmanWebsiteBucket = new s3.Bucket(
this,
'GrantStarkmanWebsiteBucket',
{
bucketName: 'grantstarkman',
websiteIndexDocument: "index.html",
removalPolicy: RemovalPolicy.RETAIN,
versioned: true,
blockPublicAccess: {
blockPublicAcls: false,
blockPublicPolicy: false,
ignorePublicAcls: false,
restrictPublicBuckets: false,
},
publicReadAccess: true,
},
);
// S3 Bucket deployment from local website/build directory
// (Not necessary for this tutorial)
new s3deploy.BucketDeployment(this, `DeployVozAmigo`, {
sources: [
s3deploy.Source.asset("../website/build"),
],
destinationBucket: grantStarkmanWebsiteBucket,
});
- We are now going to request a certificate using the AWS Certificate Manager (ACM) service. You can find this service in the AWS console by typing "Certificate Manager" in the search bar.
- Request a public certificate with a fully qualified domain name using the hosted zone domain name you created in the previous step, such as "elliotalderson.com." Set the validation method to DNS validation and the encryption method to RSA 2048.
- We will use this certificate in the next step for our CloudFront distribution.
- AWS CDK ACM Code Snippet:
1
2
3
4
5
6
7
8
9
const grantStarkmanCloudfrontSiteCertificate = new acm.Certificate(
this,
"GrantStarkmanCloudfrontSiteCertificate",
{
domainName: "elliotalderson.com",
certificateName: "elliotalderson.com",
validation: acm.CertificateValidation.fromDns(hostedZone),
},
);
- Create a CloudFront distribution with the origin domain of the S3 bucket you created in the previous step. After selecting your S3 bucket, be sure to choose the option to "Use Website Endpoint." You can leave the default settings as they are for the origin section, but for the Default Cache Behavior, be sure to select "Redirect HTTP to HTTPS" and set the cache policy to Caching Optimized. The remaining origin settings and function associations can also be left as is.
- For the Web Application Firewall, set it to "Do not enable security protections." In the final settings section, set the Alternate Domain Name (CNAME) to the domain you created in the previous step, such as "elliotalderson.com." Also, select the custom SSL certificate you created in the previous step. It may take sometime for the certificate to be issued.
- Modify your S3 Bucket Policy again in the permissions tab to your bucket. Be sure to replace BUCKET_NAME, ACCOUNT_NUMBER, and DISTRIBUTION_ID.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicReadAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/*"
},
{
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_NUMBER:distribution/DISTRIBUTION_ID"
}
}
}
]
}
- You can validate that you completed this step correctly by navigating to your Cloudfront distribution domain name, which is displayed in details section in the selected Cloudfront distribution. Your website should be displayed there.
- AWS CDK Cloudfront Code Snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const grantStarkmanDistribution = new cloudfront.Distribution(
this,
"GrantStarkmanDistribution",
{
comment: `CloudFront distribution for ${grantStarkmanWebsiteBucket.bucketName} bucket.`,
defaultBehavior: {
origin: new origins.S3Origin(grantStarkmanWebsiteBucket, {
originAccessIdentity: grantStarkmanCloudfrontOAI
}),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
},
errorResponses: [
{
httpStatus: 403,
responseHttpStatus: 200,
responsePagePath: "/index.html",
},
{
httpStatus: 404,
responseHttpStatus: 200,
responsePagePath: "/index.html",
},
],
defaultRootObject: "index.html",
domainNames: ["grantstarkman.com"],
certificate: grantStarkmanCloudfrontSiteCertificate,
},
);
- Create a Route53 an "A" alias record in your hosted zone that routes traffic to your Cloudfront distribution. You can leave the subdomain field blank.
Route53AAliasRecord - AWS CDK Route53 Code Snippet:
1
2
3
4
5
6
7
8
// Route 53 Record for Cloudfront Distribution Frontend
new route53.ARecord(this, `GrantStarkmanCloudFrontARecord`, {
zone: hostedZone,
recordName: "elliotalderson.com",
target: route53.RecordTarget.fromAlias(
new route53Targets.CloudFrontTarget(grantStarkmanDistribution),
),
});
- It's assumed that you already have a GitHub repository set up and that you have pushed your newest code changes. In your GitHub project, click the Actions tab and select “set up a workflow yourself.” This will create a
main.yml
file inside of.github/workflows/
. - Be sure to configure your AWS credentials in your GitHub repository. You can do this by navigating to the repository settings, then to Actions, and selecting Secrets and Variables. Create new repository secrets for
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,WEBSITE_BUCKET_NAME
, andDISTRIBUTION_ID
. - You can copy my workflow file directly. Just keep in mind that it utilizes NodeJS with some dependency and build steps that may not be necessary for all applications.
- The crucial step here is using the
aws s3 sync
command. Make sure to replace "website/build" with the path to your site assets. It's also essential to invalidate the CloudFront cache because the CDN caches your S3 bucket's site contents on servers worldwide.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
name: Build React App and AWS S3 Sync
on:
push:
branches:
- main
jobs:
BuildAndDeployToAWS:
name: Build and Deploy to AWS
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: '16'
- name: Install Dependencies For React App
run: npm install
working-directory: ./website
- name: Build React App
run: npm run build
working-directory: ./website
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: 'us-east-1'
- name: Sync Website Changes with S3
run: aws s3 sync website/build s3://${{ secrets.WEBSITE_BUCKET_NAME }} --delete
- name: Invalidate CloudFront Cache
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.DISTRIBUTION_ID }} --paths "/*"
- You can copy my workflow file directly.
- Now, every time you push a change, GitHub Actions will automatically update your S3 bucket contents and invalidate the CloudFront cache.
- In this blog, we covered the essential steps to host your personal portfolio website on AWS. We started by setting up a hosted zone in Route 53, created a domain, and managed the DNS settings. We then built and prepared our portfolio assets, and set up an S3 bucket to host these assets as a static website. Next, we requested a certificate from AWS Certificate Manager (ACM) to secure our site with HTTPS. We then configured a CloudFront distribution to serve our site globally with optimized caching and security. Finally, we integrated GitHub Actions for continuous deployment, ensuring that any changes pushed to our repository automatically update the S3 bucket contents and invalidate the CloudFront cache.
- Hosting your portfolio on AWS not only ensures high availability and performance but also leverages the powerful features and scalability of AWS services. I encourage you to try hosting your portfolio on AWS. The process might seem complex at first, but with this step-by-step guide, you can achieve a professional, reliable, and secure online presence that effectively showcases your skills and projects. Give it a try, and take your personal branding to the next level!