How to host your CV on https — starting from zero

A step-by-step guide using Amazon Web Services

James Koh, PhD
MITB For All
13 min readApr 3, 2024

--

Image created by DALL·E 3 based on the prompt “Draw a professional-looking cover image of a computer screen with a CV and lock symbol representing security.”

Imagine you are a hiring manager — You see a pool of CVs. The one-page all looks pretty much the same. A university degree. Internships or job experience, with dense text that you know should be taken with a pinch of salt.

Next, you see a QR code, with a candidate’s personal website address beside (something like https://jklmgroup.com/). Will you take a closer look at this candidate?

Following my guide here, you can be this candidate! Even without prior knowledge, the steps here are detailed enough for you to get this up and running today.

Overview

As promised in the last of my nature-inspired series, here’s the first article on how we can use AWS to stand out in job interviews.

Specifically, we will start off with hosting our profile on the cloud, so that it can be linked via a web address to your CV and/or any documents, for less than $1 per month (and an initial upfront costs of $5).

All it takes is the following steps, which are easy to replicate with the reader-friendly screenshots provided.

  1. Create your CV using html and css
  2. Set up AWS resources (Route 53; Certificate Manager)
  3. Make your website viewable securely (S3; CloudFront)

In addition, I will also let you know the estimated waiting time required. This is because as a new practitioner of AWS, I found the ‘pending’ status misleading — do I simply have to wait, or was the procedure not done properly? Should I wait for a minute, or 15 minutes, or two hours before trying again?

With this article, you would not have such a problem.

[1] Create your CV using html

This is actually very open-ended. In the interest of keeping things concise for better readability, I will just explain how each section of the html code (used to generate my sample website) works. The entire .css code will be shared at the end, with only brief mentions along the way.

There are plenty of crash courses on html and css around on the internet. If you are completely new to this, it is advisable to first gain a basic understanding of how these work.

At the end of this section, I offer a trimmed-down ‘starter template’ of less than 100 lines. Note that you will need to have the style.css and passport_photo.jpg files in the same directory as your .html.

Let’s now begin with a basic skeleton.

<head>
<title>James Koh</title>
<link rel="stylesheet" type="text/css" href="style.css">
<div class="sidebar">
<br>
<img src="passport_photo.jpg" alt="James Koh" class="photo">
<br><br>
<a href="#experience">Work Experience</a>
<a href="#education">Education</a>
<a href="#awards">Awards / Certifications</a>
<a href="#interests">Interests</a>
</div>
</head>

This gives the sidebar as shown below, with the mouse hovered across the ‘Education’ section. When everything is put together, clicking on any of them will lead the use straight to the section of their interest.

Screenshot by author, showing the sidebar.

Next, we have the header. Notice how I’ve created links to my LinkedIn and Medium profiles by specifying the address within href. You can also adjust the fonts by changing the font-family and font-size parameters of the relevant classes in style.css.

  <div class="content" id="contact"><br>
<h1 class="name">James Koh</h1>
<div class="contact">
<a href="https://www.linkedin.com/in/james-boon-yong-koh-894aa4138/">LinkedIn</a>
<a href="https://medium.com/@byjameskoh">Medium</a>
</div>
<h2 class="titles">Data Science Instructor | PhD</h2><br><br>
Screenshot by author, showing the header with links.

Moving forward, add the following html code to create your individual sections, as demarcated by the blue divider line. The yellow and blue boxes are defined by background-color and padding within boxA and boxB as defined in style.css, while the date is right-justified due to float:right within the dates class, and the company names appear in green due to the use of color:green within the definition of worklocations.

  <hr class="divider">
<h3 class="sectiontitle" id="experience">Work Experience</h3>

<div class="boxA">
<p class="titles"> Designation 1 <span class="dates"> Date 1</span></p>
<p class="worklocation">Company 1</p>
<ul>
<li>Point 1a</li>
<ul>
<li>sub-point 1a(i)</li>
<li>sub-point 1a(ii)</li>
</ul>
<li>Point 1b</li>
</ul>
</div>
<br>

<div class="boxB">
<p class="titles"> Designation 2 <span class="dates"> Date 2</span></p>
<p class="worklocation">Company 2</p>
<ul>
<li>Point 2a</li>
<li>Point 2b</li>
</ul>
</div>
<br>
Screenshot by author, showing the first section, along with a portion of the left side-bar.

Following the same structure, you can add more sections to your online CV according to your preference. Although having excessive information is still counter-productive, the good thing here is that you now have more room without trying to cramp everything into an A4 sheet.

    <hr class="divider">
<h3 class="sectiontitle" id="education"> Education</h3>
<p class="titles">Degree A <span class="dates">Date A</span></p>
<p class="institute">University A </p>
<p class="titles">Degree B <span class="dates">Date B</span></p>
<p class="institute">University B </p>
<br>

<hr class="divider">
<h3 class="sectiontitle" id="awards"> Awards and Certification</h3>
<p class="standalone">Organization 5</p>
<ul>
<li>Credential 5a (<a href="https://www.example.com">Link 5a</a>)
<span class="smalldates"> Date 5</span>
</ul>

<p class="standalone">Organization 6</p>
<ul>
<li> Credential 6a <span class="smalldates"> Date 6a</span> </li>
<li> Credential 6b <span class="smalldates"> Date 6b</span> </li>
</ul>
<br>
Screenshot by author, showing the subsequent sections down the page.

Putting everything together, we have the following code. If you like the layout of my site and want to follow it, you are welcome to modify the contents according to your personal life experience.

<!DOCTYPE html>
<html>
<head>
<title>James Koh</title>
<link rel="stylesheet" type="text/css" href="style.css">
<div class="sidebar">
<br>
<img src="passport_photo.jpg" alt="James Koh" class="photo">
<br><br>
<a href="#experience">Work Experience</a>
<a href="#education">Education</a>
<a href="#scholarships">Scholarships</a>
<a href="#awards">Awards / Certifications</a>
<a href="#interests">Interests</a>
</div>
</head>

<body>
<div class="content" id="contact"><br>
<h1 class="name">James Koh
</h1>
<div class="contact">
<a href="https://www.linkedin.com/in/james-boon-yong-koh-894aa4138/">LinkedIn</a>
<a href="https://medium.com/@byjameskoh">Medium</a>
</div>
<h2 class="titles">Data Science Instructor | PhD</h2><br><br>

<hr class="divider">
<h3 class="sectiontitle" id="experience">Work Experience</h3>

<div class="boxA">
<p class="titles"> Designation 1 <span class="dates"> Date 1</span></p>
<p class="worklocation">Company 1</p>
<ul>
<li>Point 1a</li>
<ul>
<li>sub-point 1a(i)</li>
<li>sub-point 1a(ii)</li>
</ul>
<li>Point 1b</li>
</ul>
</div>
<br>

<div class="boxB">
<p class="titles"> Designation 2 <span class="dates"> Date 2</span></p>
<p class="worklocation">Company 2</p>
<ul>
<li>Point 2a</li>
<li>Point 2b</li>
</ul>
</div>
<br>

<hr class="divider">
<h3 class="sectiontitle" id="education"> Education</h3>
<p class="titles">Degree A <span class="dates">Date A</span></p>
<p class="institute">University A </p>
<p class="titles">Degree B <span class="dates">Date B</span></p>
<p class="institute">University B </p>
<br>

<hr class="divider">
<h3 class="sectiontitle" id="awards"> Awards and Certification</h3>
<p class="standalone">Organization 5</p>
<ul>
<li>Credential 5a (<a href="www.example.com">Link 5a</a>)
<span class="smalldates"> Date 5</span>
</ul>

<p class="standalone">Organization 6</p>
<ul>
<li> Credential 6a <span class="smalldates"> Date 6a</span> </li>
<li> Credential 6b <span class="smalldates"> Date 6b</span> </li>
</ul>
<br>

</div>
</body>
</html>

The correspond style.css is:

.sidebar {
position: fixed;
width: 220px;
height: 500px;
background: #111;
margin: -10px;
}
.sidebar a {
padding: 8px 8px 8px 0px;
text-decoration: none;
font-size: 18px;
color: white;
display: block;
text-align: center;
font-family: "Arial Black", Gadget, sans-serif;
}
.sidebar a:hover {
background-color: gray;
}
.photo {
display: block;
margin-left: auto;
margin-right: auto;
width: 68%;
height: 38%;
}
.content {
font-family: Tahoma, Geneva, sans-serif;
margin-left: 280px;
margin-right: 20px;
width: auto;
min-width: 36em;
max-width: 60em;
height: 650px;
}
.name {
font-family: Copperplate, "Arial", fantasy;
font-size: 55px;
}
.contactlinks {
float: right;
margin-right: 20px;
}
.contactlinks a, .certs a {
text-decoration: none;
}
.contact {
float: right;
margin-right: 0px;
text-align: right;
}
.divider {
border-top: 2px solid blue;
border-radius: 2px;
}
.sectiontitle {
font-family: Copperplate, "Arial", fantasy;
font-size: 30px;
margin: 24px 0 24px 0;
}
.sectiontitlesmall {
font-family: Copperplate, "Arial", fantasy;
font-size: 30px;
margin: 20px 0 16px 0;
}
.certs {
text-align: center;
}
.titles {
font-size: 21px;
font-weight: normal;
font-family: Rockwell, "Arial", fantasy;
margin: 20px 0 0 0;
}
.standalone {
font-size: 18px;
font-weight: normal;
font-family: Rockwell, "Arial", fantasy;
margin: 20px 0 0 0;
color: green;
}
.dates {
font-size: 18px;
float: right;
margin: 0 20px 0 0;
}
.smalldates {
font-size: 16px;
float: right;
margin: 0 20px 0 0;
}
.worklocation {
font-size: 18px;
font-family: Rockwell, "Arial", fantasy;
margin: 16px 0 0 0;
color: green;
}
.skill {
color: blue;
}
.institute {
color: green;
margin: 10px 0 0 36px;
}
.boxA {
background-color: RGB(251,188,5,0.09);
padding: 2px 18px 2px 18px;
}
.boxB {
background-color: RGB(5,188,251,0.09);
padding: 2px 18px 2px 18px;
}
ul li { padding: 0px 0px 6px 0px; }

[2] Setting everything up on AWS

As the title indicates, this article is beginner-friendly and accessible to all users “starting from zero”. If you do not have an existing account, do a Google search for AWS, and create a new account following section [2a]. Otherwise, feel free to go straight to section [2b] if you already have an AWS account.

[2a] Create an AWS account

Screenshot of the AWS homepage, with option to sign up for a free AWS account.

Follow the steps, and set up your <Root user email address> and <AWS account name> accordingly. You will need to verify your email with a 6-digit verification code.

You will then be prompted to create a password for your root account, along with information such as your name, phone number, address, and credit card number.

For your security, you should adhere to the AWS best practices, such as creating an IAM user for yourself. The last thing you want would be to discover that your account has raked up a bill of hundreds or thousands of dollars, which is possible if your account has been maliciously compromised. As stated by AWS, “(we) strongly recommend that you don’t use the root user for your everyday tasks”.

The risk of incurring a large bill due to a compromised account should not hinder you for exploring the benefits that cloud computing brings. After all, risks are present in all aspects of our lives, such as when we drive or even walk across the road. The important thing is to mitigate the risk and act rationally.

[2b] Purchase a registered domain from Route 53

Intuitively, one would expect that the next step is to upload your files (created in section [1]) to the cloud. However, you need to know your domain name first, before creating your S3 bucket.

You could buy a domain from GoDaddy, and perform a few additional steps. However, the focus here is really on the most straight-forward way. At just USD13 for a basic .com domain on AWS (and USD11 for .net, or USD5 for .link) as of March 2024, I see no reason to go through the hassle of purchasing from somewhere else. Time is money!

Let’s begin.

First, go to Route 53 and click on ‘Register domains’, and search for your desired domain name. For completeness, I will replicate the whole process, and hence will purchase a new domain just to show you the steps.

Screenshot showing the available domain names. Here, .link is used instead of .com so as to keep costs low.

Select your duration (1 year without auto-renew would be the most conservative option), enter your contact information, and accept the terms and conditions before submitting.

Screenshot of the checkout page.

You would see, in the ‘Review and submit’ step, that there is a notice ‘Route 53 automatically creates a hosted zone for each new domain you register.’ That is required. It is just $0.50 per month, and you can delete it when no longer needed.

Under your Route53 dashboard, you will notice a ‘Domain registration in progress’ status. When you return in ~10 minutes, it will be changed to ‘Domain registration successful’ in green, with a tick mark.

Screenshot under Route53 Dashboard.

The domain which you have purchased would automatically created, and can be viewed under your Hosted zones (still in Route 53).

Screenshot under Route53 Hosted zones.

You would want to allow others to access your website via ‘https’ and not ‘http’, otherwise a warning sign (like below) would be the first thing that greets your viewers. That’s not good.

Screenshot of ‘Not secure’ warning when attempting to access via http instead of https.

That is why we need to perform the steps in the subsequent sections!

[2c] Certificate Manager

Go to Certificate Manager, and click on the orange ‘Request a certificate’ button. It’s prominent and you can’t miss it.

Screenshot of the output when searching for Certificate Manager on AWS.

Keep the default radio button (on ‘Request a public certificate’) unchanged, then go to the next page and enter the Fully qualified domain name. Keep to ‘DNS validation — recommended’.

Screenshot showing the process of requesting a public certificate from ACM.

You will then see a blue ribbon indicating that further action is need to complete the validation.

Screenshot of the notification at the top of ACM dashboard.

This is the somewhat tricky part for beginners. Notice the ‘CNAME name’ and ‘CNAME value’ next to your pending domain. You need to set these up in Route53.

[2d] Validate CNAME on Route 53

Go back to Route 53, and select your Hosted zone (you probably have just a single one). Click the ‘Create record’ button.

Screenshot from Route 53 Hosted zone.

Choose ‘Simple routing’ (if you are taking the AWS Solutions Architect certification, you would need to know the differences between the 8 options, otherwise you can just ignore them), then ‘Define simple record’.

Place your ‘CNAME name’ (refer back to section [2c]) into the field circled in blue, and your ‘CNAME value’ into the field circled in green. Remember to change the ‘Record type’ to CNAME from the dropdown list, as that is not the default option.

Screenshot for ‘Define simple record’.

Give it 10 minutes or so, then check back at your Certificate Manager. You should see the grey ‘pending’ status changed to a beautiful green ‘Success’ with a tick mark. (Move on to step 3[a] while waiting)

Screenshot of your domain status at ACM after 10 minutes.

With this, AWS is convinced that you are indeed the domain owner as claimed, and is happy to issue you the SSL certificate that allows you to enable https for your website.

[3] Make your website viewable, securely

We now face a sort of ‘chicken-and-egg’ situation, whereby an S3 endpoint is required in the CloudFront step (section [3b]), which in turn is required to set our S3 bucket policy properly (section [3c]).

[3a] Host your website on S3

Search for S3, which stands from Simple Storage Service.

Screenshot of the output when searching for S3 on AWS. (For the last time, I am showing that these services can easily be searched for)

Click the orange ‘Create bucket’ button, and name your bucket according to your domain name, including the top-level domain (like .com or .link).

Screenshot of page encountered after clicking ‘Create bucket’.

Leave all settings as their default, keeping ‘Block all public access’ left checked.

Upload your files (index.html, style.cssand passport_photo.jpg) to the bucket. Click on the properties segment, then scroll all the way to the bottom to edit the ‘Static website hosting’ settings.

Screenshot for bucket properties. Note that the ‘Static website hosting’ section is all the way to the bottom, and the screenshot is merged into a single figure for aesthetics reasons.

Change the radio button from ‘Disable’ to ‘Enable’, and fill the Index document field with index.html. For starters, everything else can be left untouched.

Screenshot for ‘Edit static website hosting’.

Everything here happens as fast as the click of a button, and there is no waiting required.

[3b] Set up CloudFront

Moving on, go to CloudFront, and click on the orange ‘Create distribution’ button.

In [Origin Domain Name], select your S3 static website endpoint from the dropdown options, use ‘Origin access control’ as recommended, and click ‘Create new OAC’.

Screenshot under CloudFront’s Create distribution (top of page), after creating new OAC.

Scroll further down until ‘Viewer protocol policy’, and change the radio button to ‘Redirect HTTP to HTTPS’. Signed URLs or signed cookies are more advanced stuff, so let’s skip that today.

Screenshot under CloudFront’s Create distribution (middle of page).

Under ‘Alternate domain name — optional’, input your domain name without the http or https. Finally, select your SSL certificate from the dropdown options. All other settings can be left to their default.

Screenshot under CloudFront’s Create distribution (bottom of page).

Click the orange ‘Create distribution’ button at the bottom, and you will be brought back to the dashboard with a friendly reminder to update your S3 bucket policy. Click ‘Copy policy’ and go to the link provided by AWS.

Screenshot of the top of CloudFront dashboard after creating distribution.

[3c] Set S3 bucket policy

AWS has made things convenient for us, and the link leads back to exactly where we need to be. Proceed to edit the bucket policy, and paste the text which AWS has prompted you to copy. Remember to click ‘Save changes’.

Screenshot while editing bucket policy.

Meanwhile, your CloudFront distribution status should have changed from ‘In Progress’ to ‘Deployed’ in a few minutes. Check that you are able to access your website via the CloudFront distribution. Remember to set your default root object to index.html.

Screenshot showing CloudFront distribution domain name

If you have followed my instructions, you would be able to see something like the following. Congratulations, just a final step to go before you will be able to access it via https://jameskoh.link instead of https://d2u02mw0zj1vzr.cloudfront.net/.

Screenshot of the webpage viewable from the CloudFront distribution.

[3d] Create ‘A Record’ in Route 53

In this final step, we will set things up to link our domain to the CloudFront distribution which had been created above. Go back to Route 53 and create a simple record. For Record type, choose ‘A — Routes traffic to … some AWS resources’ from the dropdown options, and route the traffic to ‘Alias to CloudFront distribution’.

Screenshot in Route 53, to create ‘A Record’.

Now.. the moment of truth!

Go to jameskoh.link (or whatever website you have created for yourself).

Screenshot of the end product, accessed via https.

And there you have it! Anyone in the world with an internet connection can check out your profile and consider hiring you!

Conclusion

In this article, I demonstrated how you can create your own online CV/website using basic html and css. The full code is provided for you to modify according to your own profile.

I then showed all the steps required to create and set-up all the relevant AWS resources, including purchasing your own domain name, so that your CV/website can be viewed a personal domain name.

In the unlikely event that you encounter an error, feel free to pose a question in the comments and I will be happy to help out.

--

--

James Koh, PhD
MITB For All

Data Science Instructor - teaching Masters students