Skip to main content

I Couldn't Leave Well Enough Alone...

·5 mins
Homelab Hugo WoodpeckerCI Docker Selfhosted CICD FOSS
Omar Amin
Author
Omar Amin
Loves boxing, FOSS and Selfhosting
Table of Contents

I don’t know what sickness I have… but I need to tinker. I can’t help myself. I wrote a whole bunch of posts in my Quest For CICD series on choosing a CICD platform and I used the example of my resume site which used to be made with Jekyll.

Well, I had a niggling problem, I couldn’t get a PDF version of my Jekyll site to look good. So I hunted around for a bit and now I have a theme that looks good on screen and in PDF… and it’s in Hugo. The other benefit to the new site, is that I found a docker image that will auto generate the PDF file. You can take a look at my CV here.

With no further ado, here’s the .woodpecker.yml:

pipeline:
  generatepdf:
    image: alpine/curl
    commands:
      - curl --request POST 'http://gotenburg:3000/forms/chromium/convert/url' --form 'url="http://hugo:1313/"' --form 'paperWidth="8.27"' --form 'paperHeight="11.7"' -o ./omaraminCV.pdf
  builddocker:
    image: plugins/docker
    settings:
      registry: <GITEA_URL>
      username:
        from_secret: giteauser      
      password:
        from_secret: giteapass      
      repo: <GITEA_URL>/omaramincv
      tags: latest
      # auto_tag: true
      dockerfile: Dockerfile
  restartcontainer:
    image: appleboy/drone-ssh
    settings:
      host:
        - dockerserver
      username: 
        from_secret: dockerserversshuser
      password:
        from_secret: dockerserversshpass
      port: 22
      command_timeout: 2m
      script:
        - cd Docker
        - docker compose pull omaramincv
        - docker compose up -d omaramincv
  mail:
    image: drillster/drone-email
    settings:
      host: smtp.gmail.com
      username:
        from_secret: email  
      password:
        from_secret: emailpass
      from: <WOODPECKER_EMAIL>
    when:
      status: [ success, failure ]
services:
  hugo:
    image: klakegg/hugo:0.107.0-ext
    commands:
      - hugo server
  gotenburg:
    image: gotenberg/gotenberg:7

branches: main                
I’ve replaced some confidential info with <> blocks.

Compared to the steps for the Jekyll site I am using a few more fun features of WoodpeckerCI. The pipeline consists of the following stages:

  1. Services startup - One of the fun features. I can start services that persist accross the pipeline steps to be used to carry out tasks
  2. Generate the PDF of my CV site
  3. Build and push the docker image
  4. Pull and restart the site container
  5. Email the build result

Services Startup

services:
  hugo:
    image: klakegg/hugo:0.107.0-ext
    commands:
      - hugo server
  gotenburg:
    image: gotenberg/gotenberg:7

services in a WoodpeckerCI pipeline are images that are run after the git repository gets pulled that can provide functions based on the internal hostname that is assigned to them. Think of them like starting little servers whose services you can access from within the pipeline. They keep running in parallel to all the tasks in your pipeline.

In the services: block above I’ve defined 2 services:

1. hugo

This runs the klakegg/hugo:0.107.0-ext Hugo image which starts up a webserver on port 1313 that renders your site from your hugo files. Notice that I don’t have to specify the pulled repository for my site, the services: block mounts it automatically. hugo: specifies that the server can be accessable from the http://hugo hostname.

The commands: block defines a set of shell commands that the image runs on startup. In this case, I am telling it to start the hugo server.

2. gotenburg

This runs a cool little image - gotenberg/gotenberg:7 that provides a server that you can query to turn any URL into a PDF with headless chrome. As with hugo, the gotenburg: code means that we will access the server on http://gotenburg

Generate PDF

  generatepdf:
    image: alpine/curl
    commands:
      - curl --request POST 'http://gotenburg:3000/forms/chromium/convert/url' --form 'url="http://hugo:1313/"' --form 'paperWidth="8.27"' --form 'paperHeight="11.7"' -o ./omaraminCV.pdf

This is the start of our pipeline tasks. The first thing we do is we use the alpine/curl image to rest the gotenburg service to create a PDF out of the site that is being served by our hugo server. the PDF is saved as omaraminCV.pdf

Build & Push Docker Image

Once the PDF is generated, we have all components we need to build the site. I am using a multi-stage Dockerfile to build the site into a container image.

  builddocker:
    image: plugins/docker
    settings:
      registry: <GITEA_URL>
      username:
        from_secret: giteauser      
      password:
        from_secret: giteapass      
      repo: <GITEA_URL>/omaramincv
      tags: latest
      # auto_tag: true
      dockerfile: Dockerfile

We are using the standard docker image from WoodpeckerCI to build the Dockerfile from our repository and then push the resulting container to my Gitea container repository.

Here is the Dockerfile that gets built. Hopefully the comments are enough to get the gist of how it works.

# This is a multi-stage Dockerfile (build and run)

# Remember to target specific version in your base image,
# because you want reproducibility (in a few years you will thank me)
FROM alpine:3.9 AS build

ENV GLIBC_VERSION 2.35-r0

# Install Glibc
RUN apk add --no-cache git openssl py-pygments libc6-compat g++ curl 

# The Hugo version
ARG VERSION=0.110.0

ADD https://github.com/gohugoio/hugo/releases/download/v${VERSION}/hugo_extended_${VERSION}_Linux-64bit.tar.gz /hugo.tar.gz
RUN tar -zxvf hugo.tar.gz 
# RUN echo $(pwd)
RUN /hugo version

# We add git to the build stage, because Hugo needs it with --enableGitInfo
RUN apk add --no-cache git

# The source files are copied to /site
COPY . /site
WORKDIR /site

# And then we just run Hugo
RUN /hugo --minify --enableGitInfo

# stage 2
FROM nginx:1.15-alpine

WORKDIR /usr/share/nginx/html/

# Clean the default public folder
RUN rm -fr * .??*

# Copy in default for webfinger
COPY _nginx/default /etc/nginx/sites-available/default

# Finally, the "public" folder generated by Hugo in the previous stage
# is copied into the public fold of nginx
COPY --from=build /site/public /usr/share/nginx/html
COPY omaraminCV.pdf /usr/share/nginx/html/

Pull & Restart Container

  restartcontainer:
    image: appleboy/drone-ssh
    settings:
      host:
        - dockerserver
      username: 
        from_secret: dockerserversshuser
      password:
        from_secret: dockerserversshpass
      port: 22
      command_timeout: 2m
      script:
        - cd Docker
        - docker compose pull omaramincv
        - docker compose up -d omaramincv

In this stage, we use the appleboy/drone-ssh image to ssh into the docker server, pull the new image and restart the container.

Email Build Result

  mail:
    image: drillster/drone-email
    settings:
      host: smtp.gmail.com
      username:
        from_secret: email  
      password:
        from_secret: emailpass
      from: <WOODPECKER_EMAIL>
    when:
      status: [ success, failure ]

The last stage gets run whether the pipeline is successful or fails and emails us details of the result.

Related

Quest For CICD - WoodpeckerCI
·4 mins
Homelab CICD WoodpeckerCI Automation FOSS Selfhosted
A deeper dive into the WoodpeckerCI pipeline
My Quest for CICD
·10 mins
Homelab CICD WoodpeckerCI Jenkins ConcourseCI Automation FOSS Selfhosted
Summary of the three CICD platforms I have tried - Jenkins, ConcourseCI and WoodpeckerCI
Quest For CICD - ConcourseCI
·5 mins
Homelab CICD ConcourseCI Automation FOSS Selfhosted
A deeper dive into the ConcourseCI pipeline