How to build a Hugo static site automatically using Drone CI and Codeberg.org

In this guide I’ll show the necessary steps to set up a Codeberg Git repository in Drone CI and run a simple build pipeline to generate and upload your static Hugo website. Anoxinon e.V. from Germany is using a very similar setup to make its blog authors' life easier.

All the steps are shown in an LXD based environment on Debian 10 Buster. Of course it will work very similarly on other Linux systems.

Drone build details screen
Drone build details screen

Preparing your Host

As Drone CI heavily depends on a Docker Environment, install the Docker Daemon on your Server (in fact, using Drone CI’s Docker image is the only official way to install it for now):

https://docs.docker.com/install/linux/docker-ce/debian/#install-docker-ce

Preparing credentials

The first credential you’ll need for your Drone setup is a random shared secret that is generated e.g. via:

openssl rand -hex 16

Note down the random string - you will need it later. The secret is used to connect the Drone main instance with a “worker” / “runner” / “agent” instance. The agent accepts build jobs and runs them in its own Docker environment. There can be more than just one agent, but a single one is sufficient for now.

Next, log in to your Codeberg.org account and enter the settings page. Create a new OAuth2 application in the “Applications” tab. You can give any title to your new Application, such as “Drone”. The “Redirect URI” must point to the Login page of your future Drone web interface. In case you’re planning to make the web interface available at https://drone.myserver.tld, the settings look like this:

Screenshot showing the settings. Name: Drone | Redirect URI: https://drone.myserver.tld/login
Screenshot showing the settings. Name: Drone | Redirect URI: https://drone.myserver.tld/login

Hit “Create Application”. You will receive

  • A client ID
  • A client secret

You will need both to connect Drone to your Codeberg repositories. Note them down carefully. As soon as you click on “Save”, the secret will never be displayed again.

Setting up startup files for Drone CI and Drone agent

Now let’s get Drone configured!

Drone is configured mainly via environment variables. There’s a lot of them, and we need to define them in the docker run command. That is why you should create two different startup Bash scripts. The first one is for the Drone main instance - the second one is for the build agent.

For the Drone main instance: create_drone_main.sh

#!/bin/bash

export DRONE_GITEA_SERVER="https://codeberg.org"
export DRONE_GITEA_CLIENT_ID=""
export DRONE_GITEA_CLIENT_SECRET=""
export DRONE_RPC_SECRET=""
export DRONE_SERVER_HOST="drone.myserver.tld"
export DRONE_SERVER_PROTO="https"
export DRONE_ALLOWED_USERS="myuser@codeberg.org, mygroup"

docker run \
  --volume=/var/lib/drone:/data \
  --env=DRONE_AGENTS_ENABLED=true \
  --env=DRONE_GITEA_SERVER=${DRONE_GITEA_SERVER} \
  --env=DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID} \
  --env=DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET} \
  --env=DRONE_RPC_SECRET=${DRONE_RPC_SECRET} \
  --env=DRONE_SERVER_HOST=${DRONE_SERVER_HOST} \
  --env=DRONE_SERVER_PROTO=${DRONE_SERVER_PROTO} \
  --env=DRONE_TLS_AUTOCERT=true \
  --env=DRONE_USER_FILTER="${DRONE_ALLOWED_USERS}" \
  --publish=80:80 \
  --publish=443:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone:latest

You need to adapt all the exported variables to your environment and your own credentials. RPC_SECRET is the “shared secret” that you created earlier with the openssl command.

DRONE_ALLOWED_USERS="myuser@codeberg.org, mygroup" defines all (Codeberg) users and groups that are allowed to register / use your Drone.ci instance.

For the Drone Docker runner: create_drone_agent.sh

#/bin/bash

export DRONE_RUNNER_NAME="local-docker"
export DRONE_RPC_PROTO="https"
export DRONE_RPC_HOST="drone.myserver.tld"
export DRONE_RPC_SECRET=""


docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e DRONE_RPC_PROTO=${DRONE_RPC_PROTO} \
  -e DRONE_RPC_HOST=${DRONE_RPC_HOST} \
  -e DRONE_RPC_SECRET=${DRONE_RPC_SECRET} \
  -e DRONE_RUNNER_CAPACITY=2 \
  -e DRONE_RUNNER_NAME=${DRONE_RUNNER_NAME} \
  -p 3000:3000 \
  --restart always \
  --name ${DRONE_RUNNER_NAME} \
  drone/drone-runner-docker:latest
  • RUNNER_NAME: Can be any name. You can choose your own :-)
  • RPC_SECRET: The “shared secret” again.

Make both files executable:

chmod u+x create_drone_main.sh
chmod u+x create_drone_agent.sh

And now let the magic happen: Start your main instance, first. Then, start your agent:

./create_drone_main
./create_drone_agent
Note that both docker run commands have a --restart always flag to auto-start after a reboot. There’s no need to run the Bash scripts ever again - except you’ve removed one of the containers e.g. because of re-configuration.

It might take a minute until both containers are initialized and ready. You should then be able to open https://drone.myserver.tld in your web browser.

Authenticating on Codeberg and activating Drone for your repos

Drone will ask you to log in to your Codeberg account or accept an access request. Allow the OAuth2 request, so Drone can access your repositories.

Make sure that your Codeberg user has sufficient access permissions on the repositories you’re using. For initial setup (such as creating WebHooks on Codeberg), the user needs Administrator access. Later the access permissions can be dropped to “Read only access”. This is important if you’re planning to use repos that you don’t own.

Back on the Drone CI main page, you should be able to see a “Sync” button at the top of the page. Click it. Your Git repositories should now appear as a list below.

Hit “Activate” on every repo which Drone should be enabled for. After confirming your request on the next page, you will will be shown the CICD settings for that repository.

If you haven’t created a repository for your static website, yet: Now it is the time to do it :-)

Creating a Drone “build and publish” pipeline for Hugo

You can leave most of the settings at their default values. We only need to add a “Secret”.

A secret in Drone terms can be any string. It can be a password, a PIN, or even an SSH key… and that’s what we are going to use it for: We will create a “secret” to securely store the SSH key that we are going to use for the website upload.

Create a user and SSH key for upload

To safely upload the generated HTML files, create a new Linux user on your webserver and generate a new SSH key for it:

adduser drone
su - drone
ssh-keygen -t ed25519
exit

Do not set a password for the key!

Then set the permissions on your webserver’s directory, so the drone user has full access:

chown -R drone:nginx /var/www/mysite.tld

Copy the SSH private key and paste it into the “Secret Value” field in your Drone repository settings:

cat /home/drone/.ssh/id_ed25519

The “Secret Name” field muss be set to drone_ssh_key. Then hit “Add a secret”.

Create pipeline in Hugo source repository

All build pipelines are controlled via a .drone.yml file (mind the dot prefix!) in their corresponding source repo. To enable the Hugo build pipeline, create a .drone.yml file in your source Repo and put the following contents inside:

---
kind: pipeline
type: docker
name: default
trigger:
  branch:
    - master

steps:
- name: Version check
  image: jguyomard/hugo-builder
  commands:
  - echo "Checking Hugo version."
  - hugo version

- name: Build 
  image: jguyomard/hugo-builder
  commands:
  - hugo --destination /drone/src/build
  - minify -r -o /drone/src/build /drone/src/build

- name: Upload 
  image: jguyomard/hugo-builder
  commands:
  - eval `ssh-agent -s`
  - echo "$SSH_KEY" | ssh-add -
  - mkdir -p ~/.ssh
  - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
  - rsync -rv -e "ssh -p 22" /drone/src/build/ drone@mywebserver.tld:/var/www/mysite.tld/ --checksum
  environment:
    SSH_KEY:
      from_secret: drone_ssh_key

If you have a basic understanding of YAML and how build pipelines work in general, you might already have an idea what’s happening here.

The build process is separated in three steps. For each step a name, an image and a set of commands is defined. The image defines a Docker image as a build environment. All the commands will be run inside that Docker container. In this example, all the commands will be run in jguyomard’s “hugo-builder” container, which includes all the needed build dependencies, such as the hugo binary, a minify binary and some SSH tools for the upload. The environment setting at the end makes sure that the SSH key secret is available as an environment variable.

To make this example code work, you need to adapt the upload path drone@mywebserver.tld:/var/www/mysite.tld/ according to your web server’s configuration

Push the new file to your Git repo, and watch Drone CI build and upload your site … :-)

The build and upload will be triggered on every push to the master branch. You change that behavior in the trigger section of the .drone.yml config file.

Happy droning!