- Published on
Running Hugo on free Ampere VM (Oracle Cloud Infrastructure)
- Author
- Written by Peter Jausovec
- Name Peter Jausovec
- @pjausovec
- A look at the free cloud services
- Creating an OCI account
- What is an OCI compartment?
- Where are OCI compartments?
- How to create an OCI compartment?
- Create the free Ampere VM
- Selecting the image and shape
- Adding SSH keys
- Connecting to the instance
- Creating a blog with Hugo
- Github action to build a static site
- Creating a new SSH key
- Adding the public key to the VM
- Store private SSH key in Github secrets
- Using a proxy to host the static site
- Configure security list and firewall
- Serving the static pages using Nginx
- Installing Certbot
- Configure Nginx to use an SSL certificate
- Conclusion
In this article, I'll take you on a journey of setting up a free-for-life virtual machine instance running on OCI. We'll be creating the account, a virtual machine instance, creating a Github repository with Hugo, setting up Nginx on the VM, and obtaining a free SSL certificate.
To follow along with the article, you'll need the following:
- Access to Github (you'll be creating a Github repository)
- A domain name
- A credit card (for creating the OCI account)
Let's get started!
A look at the free cloud services
Regardless of your opinion of Oracle as a company, you have to agree they have an extremely generous "always free" tier of cloud services.
The most exciting free services are the 4 ARM-based Ampere A1 cores and 24 GB of memory. You can use it either as a single virtual machine or split it between up to 4 virtual machines.
Here's a quick comparison of always-free resources that each cloud vendor offers. It should be obvious this is not a detailed nor an apple-to-apple comparison. The offerings and service names differ between vendors, and so do terms provided by different providers. I only picked the compute, database, and storage options for a quick comparison.
Vendor | Compute | Databases | Storage |
---|---|---|---|
AWS | 750 hours (1yr only) | 25 GB DynamoDB | 100 GB storage gateway |
Azure | 750 hours (1yr only) | Azure Cosmos DB (25 GB) | 5 GB blob storage (1yr only) |
1 e2-micro instance per month | Firestore (1 GB per project) | 5 GB-months of regional storage | |
Oracle | 4 ARM-based A1 cores and 24 GB memory | NoSQL DB (3 tables with 25 GB per table) | 10 GB object storage |
Azure has a free-for-a-year offering that includes 750 hours of B1s burstable virtual machines (Linux and Windows) but doesn't offer free compute in the form of virtual machines. However, you always get free access to Azure App Service/Static Web Apps offering to run websites/APIs. AWS also doesn't provide forever-free compute. However, you can get 750 hours free for a year.
In addition to the 4 ARM-based cores, Oracle also offers two VMs (1/8 OCPU and 1 GB memory each) and two Oracle Autonomous Transaction Processing, Autonomous Data Warehouse, and Autonomous JSON databases (two total, not each).
Oracle resets all usage limits each month. For example, 750 hours is enough to run a single VM. You can also run 2 VMs for half of the month and so on. However, make sure you do your research and read the fine print for your chosen provider.
In addition to the "always free" and "free for a year resources", all cloud providers also offer credits you can use towards cloud usage. The free credits are typically referred to as "free trial". As part of the free trial, you usually get credits (or $ amount), which you can use on additional services not part of the free resources.
Creating an OCI account
To sign-up, you pick your country/territory, enter your name and email and verify you're human (they don't want non-human users, I guess).
You'll receive a confirmation email, click on the button in the email, and you'll be taken to the second step of the sign-up process.
This is where you pick your password (Oracle, why only max 40 characters for the password?!), select your cloud account name and your home region. Note that you cannot change the home region later.
Note
Remember your cloud account name as you'll need it when logging in. You'll be prompted for the cloud account name first, followed by the username (email) and password.
The form also asks for your address, phone number, and payment verification method. The only available option is a credit card. Oracle does a temporary $1 charge to verify your card.
Finally, you'll be able to click the button that creates your cloud account.
Setting up OCI account
What is an OCI compartment?
After you've logged in, you'll end up on the OCI dashboard screen, as shown below.
OCI dashboard
Before we continue creating that free machine, let's explain what compartments are.
A compartment is a collection of cloud resources - virtual machines, databases, load balancers, basically anything you can create and run in OCI. Other cloud vendors have similar concepts. Azure, for example, uses resource groups and GCP uses projects.
By default, each cloud account has a root compartment where you could go and create resources in. However, Oracle suggests creating sub-compartments for better management of resources.
Where are OCI compartments?
To get to the compartments page:
- Open the navigation menu (top-left).
- Select the Identity & Security.
- Click the Compartments link.
OCI compartments page
On the compartments page, you'll see the list of existing compartments. Existing compartments can be renamed, deleted, and tagged. You can also move compartments (i.e., change the compartments' parent).
Note
Moving compartments has implications on the policies set on compartments and their parents. Make sure you understand what will happen before you move the compartments. The Managing Compartments explains the implications and how to work with compartments in more detail.
How to create an OCI compartment?
To create a compartment, click the Create compartment button. You can pick a name/description and the parent compartment name. In my case, I named my compartment blog and chose the root compartment as its parent.
OCI create compartment
Create the free Ampere VM
Time to create that free Ampere VM! You could split the resources (CPU and memory) across four different instances. However, I'll create a single instance of the Ampere A1. The instance uses the VM.Standard.A1.Flex shape and has 4 OCPUs and 24 GB of memory.
Note
Per Oracle pricing page, the Oracle CPU (OCPU) unit of measurement provides the CPU capacity equivalent of one physical core of an Intel Xeon processor with hyper-threading enabled. And OCPU corresponds to two hardware execution threads, known as vCPUs.
Open the navigation menu and select Compute and Instances to create the compute instance. On the instances page, make sure you select the blog compartment we created from under the List Scope section on the left side of the page. This will ensure the instance gets created in that compartment. It also serves as a filter for instances that are displayed on the page.
Selecting a compartment
Click the Create instance button to open the create page to create a new instance. There are multiple sections on the create page. However, we'll only change a couple of them.
First, you can change the randomly generated name to something more user-friendly, for example, blog-vm
. The following section is the Placement - we can keep the defaults here, but you could select a different availability domain (AD) and fault domains (FD) for the instances. Finally, the Always Free-eligible availability domain will be set by default.
Selecting the image and shape
The next section is called Image and Shape, and this is where we'll select the instance shape (instance shape is what machine type is in GCP and VM size in Azure) and the OS image for the compute instance.
The Ampere VM.Standard.A1.Flex shape only supports Oracle Linux image, so there's not much to configure here. However, if you're creating other shapes, you can pick a different image by clicking the Change image button.
Click the Change shape button to select a different VM instance shape. Make sure you have chosen the Virtual machine instance type. You can choose the shape series in the next row. We're looking for the Ampere series that contains the ARM-based processors and the shape name called VM.Standard.A1.Flex (it should be the only shape available in this view).
Select the VM.Standard.A1.Flex shape and adjust the number of OCPUs to 4 and amount of memory to 24 GB. This is what we're getting for free - forever!
Click the Select shape button to confirm the selection.
Adding SSH keys
We also want to use the SSH keys to connect to the instance later. You have an option of creating a new SSH key pair or uploading/pasting one or more existing public key files.
If you're generating a new SSH key pair, make sure you click the Save Private Key button to save the generated key.
We won't change any other settings, so let's click the Create button to create the instance.
Creating an OCI instance
After a couple of minutes, OCI creates the instance - the status of the instance changes from provisioning to running.
Connecting to the instance
To connect to the instance, we'll use the public IP address of the instance and the SSH key we've set up.
You can get the public IP address from the instances page and then use the opc
username to SSH to the machine:
$ ssh opc@192.x.xxx.xxx
...
[opc@blog-vm ~]$ uname -m
aarch64
Creating a blog with Hugo
The next step is creating a blog.
I'll use Hugo for this. Hugo is one of the popular static site generators. We can use it to scaffold a blog quickly, use Markdown to write blog posts, theme it, and finally build the static pages that can be served.
We're not going to install Hugo on the VM. Instead, we'll create a new Github repository that will store the entire Hugo site.
Then we'll use a Github action to generate the static pages and copy them over to the VM.
Assuming you've installed Hugo, let's create a new folder and a new Hugo site:
mkdir emacs.pro && cd emacs.pro
hugo new site .
The above just creates an empty site, so we need a theme. I am using a theme called hugo-coder, but you could pick any theme you like.
We'll initialize the Github repository and then add the theme as a submodule to the themes/hugo-coder
folder:
cd emacs.pro
git init
git submodule add https://github.com/luizdepra/hugo-coder.git themes/hugo-coder
We could go and customize the site, add content etc. However, let's use the example site included with the theme.
The example site using the theme we downloaded is in the themes/hugo-coder/exampleSite
folder. We need to copy all folders and files from the exampleSite
folder to the root folder (emacs.pro
). Make sure you overwrite all files when prompted. Also, edit the config.toml
file and set the baseURL to https://emacs.pro/
(replace it with your domain).
Now we're set up, and we can "run" the site locally:
hugo serve
The serve
command runs the site in the development mode on localhost:1313
. If you open localhost:1313
you should have a webpage, as shown in the screenshot below.
Basic Hugo site
The command we'll use to build the static site is hugo
. The command will create static files in the public
folder.
Let's commit all changes, create a Github repository and then push our local changes to the Github repo.
Github action to build a static site
At this point, you should have a Github repository with the initial commit of the Hugo site.
What we want to do next is create a Github workflow that starts each time we merge some changes to the main branch - for example, we modify the theme, add a new blog post, etc.
In that workflow, we need to do the following:
- Build the static site (run
hugo
) - Copy the static files (
public
folder) to the VM running in OCI
Instead of manually installing Hugo, we'll use a Github action called actions-hugo. The actions-hugo Github repository already gives us the workflow we can start with.
To create a new workflow on Github:
- Navigate to your Github repository
- Click the Actions tab
- Click the Configure button on the "Simple workflow" card
Clicking the button will open an editor to customize the workflow file. Replace contents of the editor with this YAML:
name: OCI VM Deploy
on:
push:
branches:
- main
pull_request:
jobs:
deploy:
runs-on: ubuntu-20.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.91.2'
- name: Build
run: hugo --minify
- name: Deploy
run: echo TODO
The above YAML has four steps:
- Checkout
- Setup Hugo
- Build
- Deploy
First, we check out the repository and fetch all submodules (the Hugo theme). Then, we use the actions-hugo
Github action to set up (install Hugo), and finally, we run hugo --minify
to generate the static pages and minify the files.
The last step is the deploy step, where we need to copy the generated files over to the virtual machine.
For the last step, we can also use an existing Github action. There are multiple Github actions in the marketplace, but I settled on scp-action.
To get this action to work, we'll need to give it the IP address of the VM and the private SSH key required to log in to the machine. The action will read the private key from a Github secret we'll set up later.
We've already created an SSH key earlier. However, it's not a good practice to re-use the SSH keys, so we'll make a new key.
Creating a new SSH key
Let's create a new SSH key:
ssh-keygen -t ed25519
You'll be prompted for the name and location. I named the key github_key
, but it doesn't matter the name/location as long as you remember where it is.
Alternatively, you can provide the passphrase for the key (or not). If you decide to use a passphrase, you'll also have to configure that in the scp-action. In any case, you'll end up with two files: github_key
and github_key.pub
.
The private key (github_key
) is what we'll store in the Github secrets and add the public key (github_key.pub
) to the ~/.ssh/authorized_keys
file on the virtual machine. This will allow Github (or rather the scp-action) to access the VM.
Adding the public key to the VM
Let's SSH into the OCI VM first (ssh opc@[ip-address]
). Once we're on the VM, let's copy the contents of the github_key.pub
to the end of the ~/.ssh/authorized_keys
file.
For example:
export public_key="ssh-ed25519AAAAC3NzaC1lZDI1NTE5AAAAIBVxvDS60DuGkC6KcIjayHyBeqaZMCbmYczoEJbTQfLhello@hello-world.home"
# Add the key to the authorizaed keys file
echo $public_key >> ~/.ssh/authorized_keys
Note
Replace the contents of the
public_key
variable with your public key.
Store private SSH key in Github secrets
Let's go back to the Github repo and click Settings and then Secrets and Actions.
Click the New repository secret button to create a new secret. For the secret's name, enter PRIVATE_KEY
, and in the Value text box, paste the contents of the private key you generated earlier (github_key
).
Click Add secret to save it. We can now use the PRIVATE_KEY
secret name to obtain the value of the private key.
Let's repeat the same process and add a secret called HOST
that has the value of the VMs IP address and a secret called USERNAME
with the value opc
(that's the username we used to SSH into the VM)
Github secrets
Let's go back to the new workflow page and configure the last step of the workflow.
name: OCI VM Deploy
on:
push:
branches:
- main
pull_request:
jobs:
deploy:
runs-on: ubuntu-20.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.91.2'
- name: Build
run: hugo --minify
- name: Deploy
uses: appleboy/scp-action@master
with:
rm: 'true'
source: 'public/*'
target: '/home/opc/www'
host: ${{ secrets.HOST }}
key: ${{ secrets.PRIVATE_KEY }}
username: ${{ secrets.USERNAME }}
We've configured the scp-action to copy everything from the public
folder to the /usr/share/nginx
folder on the virtual machine. We've also set the rm
to true to remove the target folder before copying everything over.
Change the file's name from blank.yml
to deploy.yml
and click the Start commit button.
After we commit the file, the workflow starts automatically. You can click the Actions tab to see the progress and the details of each step.
Once the workflow completes, it copies the contents of the public
folders to the /home/opc/www
folder on the virtual machine. We can check that by SSH-ing into the VM and looking into that folder:
[opc@blog-vm ~]$ ls www/
public
[opc@blog-vm ~]$
Using a proxy to host the static site
We have the workflow setup, the static files are on the VM, and the next thing we need to do is configure a proxy server to host the files. Because we want to make the website publicly accessible, we'll also have to update the security list in the instance details on OCI.
Configure security list and firewall
On the OCI dashboard and the instance details, click the subnet link to open the subnet details page.
Subnet details
The subnet has a default security list, and we can edit that to add an ingress rule that opens ports 80 and 443 for all IP addresses. Click on the default security list, and the Add Ingress Rules button.
In the source CIDR, enter 0.0.0.0/0
(this means all IP addresses), and for the destination port range, enter 80,443
.
Add ingress rule
Click the Add ingress rule button to add the ingress rule to the default security list. By doing this, we're allowing ingress (incoming) traffic from any IP address to ports 80 and 443.
Let's switch back to the virtual machine and run a couple of commands to open the firewall to allow HTTP and HTTPS traffic:
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload
Serving the static pages using Nginx
We've opened port 80 (and 443) to the instance IP; however, we now need to serve some pages. We'll use Nginx to serve the pages. Let's start by installing it first:
sudo rpm -Uvh https://nginx.org/packages/centos/7/aarch64/RPMS/nginx-1.20.2-1.el7.ngx.aarch64.rpm
sudo systemctl start nginx.service
To check if everything is running, you can point your browser to the instance IP address, and you should see the "Welcome to nginx!" page.
Welcome to Nginx
We're almost there! The last thing to set up is to tell Nginx where our Hugo static files are and serve that instead of the stock welcome page.
Let's edit the default.conf
file:
sudo vim /etc/nginx/conf.d/default.conf
And replace the contents with these values:
server {
listen 80;
location / {
root /home/opc/www/public/;
index index.html index.htm;
}
}
We're telling Nginx to listen on port 80 and where the root location and index HTML files are with this simple server section.
Save the changes and restart Nginx using sudo systemctl restart nginx
.
If you open the IP address now, you'll notice the page loads. However, none of the CSS/images are loaded. If you look at the errors in the browsers' console, you'll see how the requests look like:
Request URL: https://[IP]/css/coder.min.d9fddbffe6f27e69985dc5fe0471cdb0e57fbf4775714bc3d847accb08f4a1f6.css
Notice https
? It looks like we need to configure SSL and the certs. To do this, you'll need an actual domain name. I'll use one of my many domain names - emacs.pro
.
To obtain the SSL certificate for emacs.pro
and www.emacs.pro
we'll use Let's Encrypts certbot.
Installing Certbot
I ran into a bunch of issues here.
I've used snap
to install Certbot, and when trying to run it, I'd get Segmentation fault
error. That led me to this issue, which says if you're using snap to install certbot on aarch64
architecture, it's not going to work. One of the mentioned workarounds was to use yum
instead.
Installing it with yum
(e.g., yum install certbot-nginx
) fixed the segmentation fault issue but introduced another one:
[opc@blog-vm ~]$ sudo certbot --nginx -d emacs.pro -d www.emacs.pro
An unexpected error occurred:
ImportError: cannot import name constants
It turns out installing certbot-nginx
installs python2-certbot-nginx
package, which for whatever reason, doesn't seem to work either... At this point, I was too frustrated to investigate why the nginx
flavor of certbot wasn't working, so I just straight up installed certbot
only:
sudo yum install certbot
Finally, some progress! Before continuing here, make sure you go to your domain registrar and create an A record for your domain to point to the IP address of your virtual machine (e.g. A [IP] emacs.pro
). I've also created a CNAME record to point www.emacs.pro
to emacs.pro
(CNAME www.emacs.pro emacs.pro
).
At this point, I was ready to run sudo certbot certonly
to configure the web server manually.
Certbot has a handy wizard that guides you through the process. When prompted, I picked option #2 - place files in webroot directory. We already have the Nginx server up and running, so there was no need to spin up another temporary server.
I've entered my email, agreed to the Terms of Service, and entered the comma-separated domain names emacs.pro,www.emacs.pro
.
After that, I was prompted to enter the webroot for both domains. This is the same webroot where the Hugo site is, so you can enter /home/opc/www/public
.
Finally, certbot does its magic, and once it completes, you will see a message like this:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/emacs.pro/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/emacs.pro/privkey.pem
Your certificate will expire on 2022-04-30. To obtain a new or
tweaked version of this certificate in the future, simply run
certbot again. To non-interactively renew _all_ of your
certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:
...
Remember the two paths /etc/letsencrypt/live/emacs.pro
to the certs as we'll use them to re-configure Nginx.
A quick note on the certificate renewal
Let's encrypt issues certificates that are valid for three months. Ideally, you should renew the certificates before they expire. Luckily for us, there's a command called certbot renew
we can use to automatically (and non-interactively - i.e., you won't get prompted for anything) renew the certs.
I like to run sudo certbot renew --dry-run
to make sure everything is good for the renewal, but not renew anything (just do the dry run). If that succeeds, you probably want to set up a cron job that runs the sudo certbot renew
command before your certificates expire.
Configure Nginx to use an SSL certificate
With the certificate and key in /etc/letsencrypt/live/emacs.pro
folder, we can edit the /etc/nginx/conf.d/default.conf
file and set it up for SSL:
server {
server_name emacs.pro www.emacs.pro;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/emacs.pro/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/emacs.pro/privkey.pem;
access_log /var/log/nginx/localhost.access.log;
location / {
root /home/opc/www/public/;
index index.html index.htm;
}
}
Note
Note: make sure you replace
emacs.pro
with your domain name.
The difference between the previous and this config is the server name, port, and the pointers to the SSL certificate and the SSL certificate key files.
Let's restart Nginx sudo systemctl restart nginx
and go to https://www.emacs.pro. We should see the Hugo site load correctly with all CSS and images.
Hugo site on OCI VM
We have the complete scenario working now. If you make changes to the Hugo site and the PR is merged, the workflow kicks off. Github workflow builds the static site and copies the files to the VM.
Once the files are updated, the changes are visible right away on the site (since Nginx is just hosting static files).
Conclusion
Thanks for sticking all the way to the end. This was a long post. If you followed everything you should now have a free VM running in OCI and a Github repository with Hugo site.
In the Github repo we've set up a workflow that automatically builds the static site and copies it over to the VM.
One the VM side, we've configured Nginx to serve the static files as well as obtained a free SSL certificate (don't forget to renew it!).
You can check out my Github repo and workflow there.
If you liked this article, give me a follow on Twitter and join over 1000 engineers reading the Learn Cloud Native newsletter.