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
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:
- Services startup - One of the fun features. I can start services that persist accross the pipeline steps to be used to carry out tasks
- Generate the PDF of my CV site
- Build and push the docker image
- Pull and restart the site container
- 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.