Since the beginning of the year I transfer more and more of my projects to the platform Codeberg. is a Gitea alternative to GitHub with the legal form of a German non-profit association. Since then, I have also joined the association as a member.

Today I want to talk about my website I was looking for a long time to be able to create a static website without much effort. Quickly I found what I was looking for - Hugo. With the help of a suitable theme, which was designed by the Hugo community, the website was quickly ready.

To generate the static website, I previously used GitHub Actions to build a Docker image, which I then hosted via my VPS at Netcup. However, since the overhead here was quite large, it didn’t seem like the most efficient solution.

First of all, I would like to thank Jan Wildeboer, who gave me the idea. In his blog post, he described the possibility of using the Woodpecker CI used at Codeberg to generate static pages and host them with Codeberg Pages. “Codeberg Pages allows you to easily publish static websites with a human-friendly address ({user-name} via Git on” In addition, custom domains can be used for this purpose, but more on that later.

Let’s take a closer look at my woodpecker pipeline file .publish.yml file:

# .publish.yml
    image: woodpeckerci/plugin-git
      recursive: true

As the first step of the pipeline, the repository must be cloned. This step is usually already present automatically and does not need to be explicitly declared. However, since I have included the Hugo theme via a Git submodule, the repository must always be cloned recursively.

  image: markdownlint/markdownlint
    - mdl --version
    - mdl content

After cloning the files, I first use the tool markdownlint, a tool to check markdown files and flag style issues. The pipeline is terminated directly as soon as a rule in the Markdown files is violated.

    image: klakegg/hugo
      - hugo --minify

Building the website into static pages is done by klakegg’s hugo container. Woodpecker uses shared directories, which are accessible at every step of the pipeline. The files are stored in the public directory.

    image: bitnami/git
    secrets: [ cbmail, cbtoken ]
      - HUGO_OUTPUT=public
      - git config --global $CBMAIL
      - git config --global "Woodpecker CI"
      - git clone -b pages https://[email protected]/$CI_REPO.git $CI_REPO_NAME
      - cp -ar $HUGO_OUTPUT/. $CI_REPO_NAME/
      - cp .domains $CI_REPO_NAME || true
      - cd $CI_REPO_NAME
      - git add .
      - git commit -m "Woodpecker CI ${CI_BUILD_CREATED}"
      - git push
      event: push
      branch: main

The final step is to push the site to the pages branch in the same repository. This is done by committing the contents of the public folder. For this purpose, an access token has to be created and stored as pipeline secret, so that the worker can push to the repository. This creates a nice workflow to add new blog entries or other changes, as a commit to the source code triggers a direct republish of the page.

You can find the latest version of the pipeline in my repository

If you have any questions or suggestions for improvement, feel free to contact me through my usual channels.