Continuous Hugo delivery with droneci

When deciding for a hosting/blog platform I always enjoyed the concept of statically generated websites. It’s simple, it doesn’t put much stress on the web/application server to run complicated scripts and content management becomes dead simple through the use of git or any VCS. One of the downsides compared to traditional platforms has been the lack of on the fly editing more or less, since getting the generator application to run on a phone for example might prove difficult.

Cue hugo + gitea + drone.

This setup will use a remote gitea repository holding your hugo build files, a droneci instance building and deploying Hugo (including build notification) to enable you to create from any machine that can edit text and interact with git.

Requirements:

  • Hugo
  • git(ea)
  • Docker
  • Discord/Slack/Email (optional)

Hugo

Setting up Hugo, albeit being fairly simple, is not the main focus of this. Install Hugo as described here and follow the quickstart guide for a basic setup.

Initialize a git repository in the hugo folder, add the hugo output directory (public/ by default) to your .gitignore, add your gitea remote, set it as upstream url and set up git to automatically push after commiting via:

echo "git push" > .git/hooks/post-commit && chmod +x .git/hooks/post-commit

Now every edit or new post will be automatically pushed to the remote repository after committing it and Drone will pick it up.

Drone

Setting up Drone is rather simple, it only is available as a Docker (compose) orchestration. Following the exemplary docker-compose.yml:

version: '2'

services:
  drone-server:
    image: drone/drone:0.8
    ports:
      - 8000:8000
      - 9000
    volumes:
      - drone-server-data:/var/lib/drone/
    restart: always
    environment:
      - DRONE_OPEN=false
      - DRONE_ADMIN=%adminaccount/giteauser%
      - DRONE_HOST=${DRONE_HOST}
      - DRONE_GITEA=true
      - DRONE_GITEA_URL=%yourgiteainstanceurl%
      - DRONE_SECRET=${DRONE_SECRET}

  drone-agent:
    image: drone/agent:0.8
    command: agent
    restart: always
    depends_on:
      - drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_SERVER=drone-server:9000
      - DRONE_SECRET=${DRONE_SECRET}

volumes:
  drone-server-data:

This configuration will have the Drone container offer HTTP on port 8000 of the host where you can use it as is or put it behind a proper webserver as proxy.

Autostart orchestration with systemd

The following /etc/systemd/system/drone.service unit allows autostarting of the docker orchestration:

[Unit]
Description=Drone Docker image
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
Environment=DRONE_HOST=%DRONE_HOSTNAME%
Environment=DRONE_SECRET=%DRONE_SECRET%
WorkingDirectory=/var/lib/docker/applications/drone
ExecStart=/usr/bin/docker-compose up -d
ExecStop=/usr/bin/docker-compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

The %DRONE_HOSTNAME% and %DRONE_SECRET% environment variables will be forwarded to the docker containers in the defined places. The WorkingDirectory option should naturally point to the directory containing the docker-compose.yaml for your drone orchestration.

Configuring drone secrets

In order to not put authentication information into (possibly) public repositories, drone allows for setting secrets in the backend via the drone cli client or the repository settings in the drone webinterface ($DRONE_HOST).

Through the webinterface, open the repository, open its settings and the secrets tab. Input secret name and value as required (see below or plugin documentation for the plugin-specifically required names of secrets).

Via the drone CLI client, secrets can be added to a repository with the following commands1:

DRONE_SERVER=%DRONE_HOSTNAME% \
DRONE_TOKEN=%DRONE_TOKEN% \
drone secrets add -repository <repo/name> -name <secret_name> -value <secret_value

Configuring the drone build pipeline

Adding the following .drone.yml into your hugo repository triggers rebuilds on pushed commits through the drone-hugo plugin, copy the built static files to your webserver through SCP and notify you about the build status on various channels2:

pipeline:
  build:
    image: cbrgm/drone-hugo:latest
    validate: true
    output: public
    url: %HUGO_INSTANCE_URL%
  scp:
    image: appleboy/drone-scp
    host:
      - %SCP_HOSTNAME%
    port: %SCP_HOST_PORT%
    username: %SCP_USERNAME%
    target: %SCP_TARGET_DIRECTORY%
    source: public/
    secrets: [ ssh_key ]
  mail:
    image: drillster/drone-email:latest
    host: %SMTP_HOSTNAME%
    username: %SMTP_USERNAME%
    secrets: [ email_password ]
    from: %SENDER_ADDRESS%
    recipients: [ %RECIPIENT_ADRESS% ]
    when:
        status: [ success, changed, failure ]
  mattermost:
    image: plugins/slack
    webhook: %SLACK_WEBHOOK%
    channel: %SLACK_CHANNEL%
    username: droneci
  discord:
    image: appleboy/drone-discord
    webhook_id: %DISCORD_WEBHOOK_ID%
    webhook_token: %DISCORD_WEBHOOK_TOKEN%
    username: droneci

  1. The Drone token is available from your Drone account token settings; https://droneinstance/account/token. [return]
  2. [return]
drone  ci  docker  hugo  git  gitea