Building a Docker Container with Gitea Actions on K3s

Building a Docker image and pushing it to the registry with GitHub Actions is incredibily easy. Since Gitea Actions are designed to be compatible with GitHub Actions, this should be easy, right?

Gitea Actions

Gitea Actions is a CI/CD solution tightly coupled with Gitea. They have been available since Gitea 1.19 and are designed to be mostly compatible with GitHub Actions. They are based on the act, which allows you to run GitHub workflows locally. Gitea has soft forked it to create act_runner.

To use Gitea Actions on you instance, you need to first allow them in app.ini. Then create a token and deploy the runner. Once the runner is deployed and registered, you will also need to enable Actions for each repository separately. The Actions runner is a self-contained system - a docker container that, for each job, launches a new container inside which the action steps are run. For a full guide on setting up Actions, check the official Gitea docs.

Building and Pushing Docker Image with GitHub Actions

With GitHub Actions, you can make use of thousands of actions available in GitHub Marketplace. If you want to build a Docker image on GitHub, you can just use the official Docker build-and-push action. Just copy one of the examples and you are good to go.

Building with Gitea

In order to enable Gitea Actions, you nedd to first deploy the Actions runner. I followed the Kubernetes example from gitea/act_runner repository. The deployment is simple. The runner will register itself with your Gitea instance, and after you enable Actions globally and for each repository, you’ll be able to try Actions.

The first thing I tried was the same workflow that I used on Github. That didn’t work. The first step to fail was docker login action. It complained that it couldn’t find the docker command. It turns out that the default container image in which the Actions runner runs the commands did not contain docker. I tried manually installing it, but a simpler solution was to just specify a different container by catthehacker, which already has docker preinstalled. After switch to the new container, the logging in worked fine.

The next problem was with setting up the docker buildx action. It couldn’t connect to Docker daemon at unix:///var/run/docker.sock. After much debugging, trying different things, and searching the internet, I found out that because the docker-in-docker runner container is rootless, the Docker socket is at unix:///var/run/user/1000/docker.sock instead. I just needed to change the DOCKER_HOST environment variables. I also removed DOCKER_TLS_VERIFY and DOCKER_CERT_PATH environment variables since they weren’t necessary.

Okay, so now, everything should work fine, right? Not so fast. Apparently the Docker buildx action makea some assumtions about the system, which work well in a well-defined environment of GitHub Actions but don’t necessarily hold true for self-hosted K3s deployments. It complained that it couldn’t mount sysfs to rootfs at /sys due to operation not permitted. The solution was to run docker commands directly instead of using buildx action.

The last hurdle was to pass the login secrets to the action. Gitea does not yet support an equivalent to GITHUB_TOKEN, so instead, I needed to manually create a token and add it to action secrets as REGISTRY_TOKEN.

This is a very condensed summary of many hours spent debugging, searching, and trying to make Gitea Actions build a Docker image on K3s. I’ve skiped a few different attempts that lead to nowhere, such as using RedHat’s Buildah instead of Docker. In the end the actual solution was much simpler than any of my attempts.

TL;DR

To build a Docker image using Gitea Actions on K3s deploy the dind-rootless Actions runner with these environment variables:

        env:
        - name: DOCKER_HOST
          value: unix:///var/run/user/1000/docker.sock
        - name: GITEA_INSTANCE_URL
          value: http://gitea-http.gitea.svc.cluster.local:3000
        - name: GITEA_RUNNER_REGISTRATION_TOKEN
          valueFrom:
            secretKeyRef:
              name: runner-secret
              key: token

Create release.yaml file in .gitea/workflows folder. For building and pushing the image, use docker commands directly. For example:

name: Build docker container
on:
  push:
    branches:
      - main

jobs:
  build:
    name: Build image
    runs-on: ubuntu-latest
    container: ghcr.io/catthehacker/ubuntu:act-latest
    env:
      IMAGE_NAME: example-image
      REGISTRY: example.com
      REPO_OWNER: test
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          registry: example.com
          username: ${{ gitea.actor }}
          password: ${{ secrets.REGISTRY_TOKEN }}
      - name: Build and push
        run: |
          TODAY=$(date +'%Y-%m-%d')
          docker build -t ${REGISTRY}/${REPO_OWNER}/${IMAGE_NAME}:${TODAY} -t ${REGISTRY}/${REPO_OWNER}/${IMAGE_NAME}:latest .
          docker push ${REGISTRY}/${REPO_OWNER}/${IMAGE_NAME}:${TODAY}
          docker push ${REGISTRY}/${REPO_OWNER}/${IMAGE_NAME}:latest