Automated deployment of Hugo with Vultr VPS and Github Actions - Part 2

Automated deployment of Hugo with Vultr VPS and Github Actions - Part 2

This is part 2 of this series where we’re going to get traefik up and running on our VPS.

In this part we will be setting up our Vultr VPS and building out the docker image for our application proxy, traefik. This will handle routing our users to the correct docker container. By the end you should have VPS that has docker-compose installed as well as traefik and its dashboard page which will give us some handy metrics for all our deployed docker services.

While the steps are specific for Vultr, you should be able to follow them for any VPS provider.

Don’t forget to check out Part 1

 

Create a Vultr Account

If you haven’t already done so, sign up to Vultr or your favourite VPS provider (although this blog is purely related to Vultr).

 

Create SSH Key and add it to Vultr

We want to ensure that only we can access our VPS in a secure way, so what better way then using SSH keys.

To make things easy I wont cover how to generate a new SSH key pair, because Vultr already has some excellent documentation on how to do that depending on your OS.

After you’ve generated a new EdDSA keypair go ahead and add it to your account.

This will save us some trouble of trying to do this after we deploy our new server.

 

Deploy our new Vultr VPS

Now that we’ve got that out of the way we can go ahead and deploy a new server. Deploy New Vultr Server

Choosing the server configuration will boil down to exactly what type of blog you’re setting up and how much traffic you anticipate it getting. For most cases, the cheap $5-$6 (AUD) instances will be plenty enough.

 

Server

There are really only 2x appropriate options for us to choose. Cloud Compute and High Frequency.

The main difference between the two is the storage options. The cheaper Cloud Compute utilizes SSD drives, where as the High Frequency instances use NVMe drives.

Personally the difference between the 2x cheapest options for these isn’t a great deal and the added storage and extra speed of the high frequency instances is what I would go for.

 

Server Location

This is fairly straight forward, its the geographic location of where your server will be located.

The main decision here will be where your user base is. Closer server === faster site.

 

Server Type/OS

Vultr gives us a bit of a head start here by having an image ready for us to use that already has docker installed and running.

The docker application that Vultr has uses a Linux based OS of your choice. This should be whatever you’re most comfortable with, CentOS or Ubuntu.

So for me, thats the Marketplace Apps > Docker > Ubuntu 18.04

 

Server Size

As mentioned above this will be dependant on the type of blog you’re wanting to host. But generally speaking the smallest option will be perfectly fine.

So lets go with:

SpecValue
Storage32GB NVMe
CPU1
Memory1024MB
Bandwidth1000GB

 

SSH Keys

Make sure you select the SSH keypair that you added earlier, this will automatically deploy the new server with the public key already added to the authorized_keys file for ssh.

 

Miscellaneous

You can give your server a name and provide a tag, and if you have multiple VPS deployed then this would definitely be recommended, otherwise just leaving them blank and Vultr will auto populate these with generic values.

 

Installing docker-compose

After a few minutes your new VPS will be up and running. You can access its details from your dashboard and open your favourite ssh client to connect to it using the ssk keypair that we loaded from earlier.

 

House keeping

We want to make sure that the VPS is fully updated before we proceed, so depending on the OS go ahead and get the existing packages updated.

Ubuntu

sudo apt upgrade

CentOS

sudo yum update

While we’re here, lets also follow some security best practises by creating a new non-root user and disabling password authentication for the server. Vultr has an excellent write-up on the steps required to do this, so I wont cover them here.

 

Installing docker-compose

Lets go ahead and get docker-compose installed, first we need to make sure that we have the pre-requisites installed, which means python3 and the latest pip3.

sudo apt install -y python-setuptools python3 python3-pip
python3 -m pip install -U pip

Once that’s done we can go ahead and install docker-compose and validate that its installed.

sudo pip3 install docker-compose
docker-compose -v

 

Configuring Traefik

Now that we have docker-compose installed, we can go ahead and create our folder structure, update our .gitignore and initialize our git repository and starting writing out some of our infrastructure/container configuration! 😁

Go ahead and create the below folders and files, we’ll add hugo into the mix later.

mkdir traefik
touch .gitignore
touch docker-compose.yml
touch traefik/traefik.toml
touch traefik/dashboard.toml
touch traefik/acme.json && chmod 600 traefik/acme.json

And you should end up with something like this:

hugo-and-traefik-docker-compose
    |   .gitignore
    │   docker-compose.yml

    └───traefik
            acme.json
            dashboard.toml
            traefik.toml

We will be using Lets Encrypt to automatically request and renew our SSL certificate over the acme protocol.

git init
git add .
git commit -m 'setup initial folder structure'

 

Traefik Config (Docker)

We will need to ensure that we create a docker network for traefik to use, and later we will also be adding our hugo container to the same network. You can call it whatever you want, but for simplicity lets just call it web.

docker network create web

Go ahead and update our docker-compose.yml file with the below, and ensure you update the volume paths to match where you’re storing your files (/opt/srv in our example)

version: "3.3"
services:
  traefik:
    container_name: traefik
    restart: always
    image: traefik:2.4.8
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/srv/traefik/traefik.toml:/traefik.toml
      - /opt/srv/traefik/dashboard.toml:/dashboard.toml
      - /opt/srv/traefik/acme.json:/acme.json
    networks:
      - web
    ports:
      - 80:80
      - 443:443
networks:
  web:
    external: true

The main things to note:

  • Using the official traefik docker image (v2.4.8 at the time of writing)
  • We’re assigned the web network we created earlier to the service
  • Ports 80 and 443 are exposed
  • Mapping some files/volumes from our host VPS into our container (we will use these to configure traefik next)

 

Traefik Config (Application)

Traefik has 2 different types of configurations:

Static

You can think of the static configuration as the run time configuration. This tells traefik what ports (entry points) to listen for incoming requests on as well as where it should route that requests to (providers), so in our case, this will be docker.

We will also add our SSL configuration for HTTPS using Lets Encrypt here as well, as this will apply to every incoming request.

Traefik will automatically look for a file called traefik.toml containing the static configuration, hence why we’ve added that.

 

Dynamic

The dynamic configuration’s are where we define things like IP whitelisting, Basic Authentication (middleware) or our destinations (routers).

Because we’re going to add the traefik dashboard to give us some cool metrics, we will configure this via a file provider (instead of docker tags) which will also include some middleware.

Normally you would separate the middleware and routers into different files, however because we are configuring traefik with docker we can just setup labels in our docker-compose for configuring routers and certificate resolvers to use.

So with that out of the way, go ahead and add the below to our traefik/traefik.toml file.

[entryPoints]
  [entryPoints.web]
    address = ":80"

    [entryPoints.web.http.redirections.entryPoint]
      to = "websecure"
      scheme = "https"

  [entryPoints.websecure]
    address = ":443"

[api]
  dashboard = true

[certificatesResolvers.lets-encrypt.acme]
  email = "email@domain"
  storage = "/acme.json"

  [certificatesResolvers.lets-encrypt.acme.tlsChallenge]

[providers.docker]
  watch = true
  network = "web"

[providers.file]
  filename = "dashboard.toml"

Running through the configuration from top to bottom:

  • We have an entry point of web (which is anything on port 80).
  • We have an entry point of websecure (which is anything on port 443).
  • For anything that comes into our web entry, we perform an automatic redirection to websecure, which is https
  • We enable the included traefik dashboard
  • We setup a certificatesResolver called lets-encrypt using acme
    • This should be configured with an email that the certificate gets registered to
    • As well as configuring where the certificate should get saved (acme.json)
    • We also configure the tlsChallenge as the renewal method
  • We configure the docker provider
  • We configure our dasboard as a file provider

And lastly lets configure our traefik/dashboard.toml file:

[http.middlewares.simpleAuth.basicAuth]
  users = [
    "admin:$2y$05$P4sZn4MA3E/frEoJP4EhFuuQpbZiTlwBi2OjEPdoAa/yroJwCJYaG"
  ]

[http.middlewares.dash-whitelist.ipWhiteList]
  sourceRange = ["xxx.xxx.xxx.xxx"]

[http.routers.api]
  rule = "Host(`admin.yourdomain.com`)"
  entrypoints = ["websecure"]
  middlewares = ["simpleAuth", "dash-whitelist"]
  service = "api@internal"
  [http.routers.api.tls]
    certResolver = "lets-encrypt"

Running through the above, we have a few things we need to take care of:

  • We have a middleware called simpleAuth that forces a basic authentication prompt
    • We configure a username/password that is required to access the page (more on this below)
  • We have another piece of middleware called dash-whitelist that allows us to provide an IP whitelist
  • Then we configure out dashboard (the default api router if enabled)
    • Any request that come in on admin.yourdomain.com, route it to this router
    • Only allow it to be accesible on the webecure entrpoint (https/443)
    • Apply the simpleAuth and dash-whitelist middlewares to the router
    • Define the service as the internal api@service
    • Setup the certificate resolver to use our lets-encrypt resolver

There are two things that you should update in the above, the first is the basic auth user that is allowed to access the dashboard, the second is the ip address that is allowed to access the dashboard.

We want to ensure that only the admin can access the dashboard, which is why we’re adding both a username/password to the site as well as restricting it to a specific IP address.

 

Add an admin user

In order to add a username/password we can utilize htpasswd as per the docs. Go ahead an install htpasswd if it isn’t already.

sudo apt install apache2-utils

Once its installed we can create a password hash, based on a specific username, in the below case admin. Copy the result into the dashboard.toml file.

htpasswd -nB admin
admin:$2y$05$P4sZn4MA3E/frEoJP4EhFuuQpbZiTlwBi2OjEPdoAa/yroJwCJYaG

 

Add your IP address

Go ahead and check what your public ip is and add it to the dashboard.toml file.

 

Update your DNS

Now that we’ve got traefik ready to role on our VPS, we can go ahead and add an A record to our domains DNS registrar that will map the admin.yourdomain.com to the IP address of our Vultr VPS.

Once thats done and the DNS records have propogated across the internets (this could take up to a day depending on your DNS registrar) we can go ahead and test 🤞 🥳

 

Testing!!

Lets go ahead and bring our docker container up in a detached state:

docker-compose up -d

And now if we browse to our newly configure traefik dashboard admin.yourdomain.com, we should get a password prompt, and then we should land on the traefik dashboard!

Traefik Dashboard

Give yourself a pat on the back for setting up traefik with docker and securing it with some middleware and lets-encrypt! 🍺🍺🍺

 

Closing

Congrats! There was a lot in this blog, hopefully you’ve gotten something out of it and can see the power that traefik gives us with a few simple configuration files.

Stay tuned for Part 3 covers getting Hugo added to our docker-compose and we implement our CI/CD pipeline in github 😁

 

Troubleshooting

If for whatever reason you aren’t treating yourself to a beer (hopefully soon), lets see where you went wrong.

Check the docker logs

Lets first make sure that docker is happy and traefik successully loaded our configuration. Run the below command to see what our docker container is doing:

docker logs traefik

This could reveal a problem with it not finding or mounting our traefik.toml configuration, or there could be a problem with lets-encrypt not successfully generating a new certificate for us

 

Check our lets encrypt certificate

If everything went well with lets encrypt then we should see our certificate inside our acme.json file, lets check:

cat traefik/acme.json

You should see the registration account/email as well as the certificate key

 

Username/Password not working?

Double check that the username and password match what you entered when trying to [generate a new password]({{< ref “#add-an-admin-user” >}} “Generate a new password”)

 

403 Forbidden?

Receiving a forbidden message? This will be our IP Whitelisting at work. Double check that you entered the correct IP address