I've been working with Civo for a year now, and the truth is that I can't ask for more - it's great working for a cloud company like Civo and with great co-workers. I get to play around with the technology and when I discover something cool, I can write a guide on it. So here is another one! By the end of this you will have a blog site running on Kubernetes that you can continuously deploy without interruptions with each commit to your repository.

What is Hugo?

According to the official site:

The world’s fastest framework for building websites

Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.

The Core

To follow along with this guide, you will need to have a Civo account. You can sign up here and if approved, you will get $250 monthly statement credit for 1 month, meaning following along will be entirely free for you!

You will also need a GitHub account where you can set up a repository, your Civo API key and a Docker Hub account.

We will skip explaining k3s, GitHub Actions, and how they relate together for now. If you want to read about what k3s is, check out this article, or this one on GitHub Actions specifically.

Install Hugo

The first thing will be to download Hugo from their GitHub repository releases page. There are many options for various use cases and operating systems. We will use the hugo_extended version that has all that's necessary for development without having to install anything else. Download the latest version of hugo_extended for your operating system, expand it to a directory of your choice, and add that path to our PATH. Now, to test if everything is fine we execute this command, and you should see output like below:

% hugo version
Hugo Static Site Generator v0.74.3-DA0437B4/extended darwin/amd64 BuildDate: 2020-07-23T16:28:32Z

In my case I have the version v0.74.3 and as I'm on a Mac I use darwin/amd64. Your output may be different if you use another OS or the version number has changed.

Our first site

Once the installation of hugo is ready, we can create our first site, and we will do it with this command:

% hugo new site k3s-demo

Congratulations! Your new Hugo site is created in /hugo/k3s-demo.

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

Visit for quickstart guide and full documentation.

As the message says, we can go to the themes page and choose one that we like for our demo. I chose PaperMod and we are going to install it by running the following command inside our project directory:

git submodule add k3s-demo/themes/papermod

If it gives an error, it's most likely because your site is not in a git repository, which the command expects. Simply run git init to initialise a repository in your project's root directory. It is a good idea to create the corresponding GitHub repository now as well. After this we can enter the folder themes/papermod. Inside will be a directory called exampleSite, under which we will find a file called config.toml. Copy this file to the root of our project:

k3s-demo $ cp themes/papermod/exampleSite/config.toml .

Once this is done, standing at the root of our project we are going to run hugo in development mode like this:

% hugo serve

                   | EN
  Pages            |  6
  Paginator pages  |  0
  Non-page files   |  0
  Static files     |  6
  Processed images |  0
  Aliases          |  1
  Sitemaps         |  1
  Cleaned          |  0

Built in 44 ms
Watching for changes in /hugo/k3s-demo/{archetypes,content,data,layouts,static,themes}
Watching for config changes in /hugo/k3s-demo/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address
Press Ctrl+C to stop

If you visit http://localhost:1313/, you should see something like the following:


So as not to complicate things for this guide, we are going to leave our site as is for now and proceed to the GitHub action. You will be able to add content once that is ready.

GitHub Actions

Now we are going to create everything necessary for our site to run in live Kubernetes. To start, we are going to need to create a repository for our site on GitHub.

Following this we are going to create this directory structure in the root of our site:

├── .github
│   └── workflows
│       └── main.yaml

After this we will create the secrets within our repository's settings on GitHub. You will need the following:

CIVO_TOKEN <-------- | Your Civo API token
DOCKER_EMAIL <------ | Your Docker Hub email
DOCKER_USERNAME <--- | Your Docker Hub username
DOCKER_TOKEN <------ | A token generated in Docker Hub's settings page

Once this is done, we are going to put the following content inside ./github/workflows/main.yaml, which will form the steps of our GitHub deployment action. It is set to run on changes to the main branch (labeled master in this case) by installing the Civo action that allows you to create a cluster. It the Dockerizes your Hugo site and applies that Docker container to the cluster.

name: Deploy-Site

    - master
    branches: [master]

    runs-on: ubuntu-latest

    - uses: actions/checkout@v2
    - name: Install civo
      uses: civo/action-civo@v1.0.0
        token: ${{ secrets.CIVO_TOKEN }}

    - name: Create a k3s cluster
      run: >
        if [[ $(civo k3s show my-site -o custom -f ID) == "" ]]; then
            civo k3s create my-site -n 2 --wait

    - name: Make config folder
      run: mkdir ~/.kube

    - name: Save our cluster's authentication details
      run: >
        civo k3s config my-site --save
        --local-path ~/.kube/config -y

    - name: Ensure we can connect to the API
      run: >
        while [ $i -le 120 ]; do
          kubectl get nodes && break;
          sleep 1;

    - name: Authenticate our Kubernetes cluster to Docker Hub
      run: >
        if ! kubectl get secret regcred | grep "regcred"; then
          kubectl create secret docker-registry regcred

          --docker-username=${{ secrets.DOCKER_USERNAME }}
          --docker-password=${{ secrets.DOCKER_TOKEN }}

    - name: Replace our cluster ID
      run: >
        sed -i'' -e "s/CLUSTER_ID/`civo k3s show my-site -o custom -f ID`/" k8s.yaml &&
        sed -i'' -e "s/CLUSTER_ID/`civo k3s show my-site -o custom -f ID`/" config.toml

    - uses: jakejarvis/hugo-build-action@master
        args: --minify --buildDrafts

    - name: Push to DockerHub
      uses: docker/build-push-action@v1
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_TOKEN }}
        repository: alejandrojnm/my-site
        tags: latest

    - name: Deploy our app to the cluster
      run: kubectl apply -f k8s.yaml

    - name: Wait for the deployment to be ready
      run: >
        while [ $i -le 120 ]; do
          kubectl rollout status deployment/my-site | grep "successfully rolled out" && break;
          sleep 1;

    - name: Update application
      run: kubectl patch deployment my-site -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"`date +'%s'`\"}}}}}"

In the above file you will only have to modify repository: alejandrojnm / my-site to include the repository you are going to use.

Now we are going to create the parts necessary to integrate everything. The first thing will be to create the Dockerfile file in the root of our project, with the following:

FROM nginx
COPY ./public /usr/share/nginx/html

This simply sets up a nginx web server to serve our site's public folder.

Following this we will create another file called k8s.yaml and inside we will put this:

apiVersion: apps/v1
kind: Deployment
  name: my-site
  replicas: 3
      app: my-site
        app: my-site
        - image: alejandrojnm/my-site:latest
          name: my-site
          imagePullPolicy: Always
        - name: regcred
apiVersion: v1
kind: Service
  name: my-site
    app: my-site
    - name: "my-site"
      port: 80
    app: my-site
kind: Ingress
  name: my-site-ingress
  annotations: "traefik"
    app: my-site
    - host:
          - path: /
              serviceName: my-site
              servicePort: 80

As with the earlier example, in this file you will also have to modify - image: alejandrojnm/my-site:latest to use the one that you put in the GitHub action. The other steps this deployment file takes are to create a service operating on port 80 and an ingress allowing routing to your deployed site.

Now our last step, remember the config.toml of our site that is at the root of our project? Well, we are going to modify it by replacing baseURL with this baseURL = "".


Now we only have to do commit and push to our repository and if everything went well, within a few minutes we will be able to see our Hugo site in the url This site would get updated with new content, themes, or anything else you change in the repository, with each push to the main branch.

Next steps

Of course, this example is very simplistic. It assumes your site should exist in a Kubernetes cluster called "my-site", for one. It also makes the decision of the type and number of Civo Kubernetes nodes to deploy on your behalf. It also does not enable https, but that is best left to another guide.

You're free to play around with the values in the various yaml configuration files to get a deployment that suits your use case. Just make sure that if you are changing your cluster specifications you remove it from your Civo account. If you are simply changing configuration options, each time you commit the files and push to master the GitHub action will simply update rather than rebuild your cluster according to your specifications.

Let us know on Twitter or on the Civo Community Slack if you followed along, and where we can see your blog on the internet!