3 min read

Gitea Actions Container Builds

Gitea Actions Container Builds
Generated with DALL-E

For a long time, I wanted to have a well-integrated Open-Source CI/CI Pipeline solution for my personal Gitea code hosting. There are many options out there (Drone, Woodpecker, Jenkins, Concourse, Screwdriver, and a lot more), but none of them made my day.

Ever since Gitea Actions were announced, I was eager to get to know them and use them. Now was the time for it.

For Gitea Actions to work, they have to be enabled globally in a feature flag, as well per project. See the official documentation to learn more.

Of course, you also need a runner to execute the jobs, and this is where it gets interesting: I wanted to have this runner on my Kubernetes cluster. There is an example in the act_runner repository (official Gitea Actions runner) how to deploy it in Kubernetes. It basically spins up a Pod with two containers: In one container the runner itself is running and in the other one there is Docker-in-Docker running, which gets used by the runner to execute the commands.
The integration in Kubernetes is therefore very bare-bones, in a real integration it would run the jobs as Kubernetes Pod without the need of Docker-in-Docker.

Now it gets interesting for the second time: To successfully build a container image in a Gitea Action, I use Docker Buildx which natively supports running builds in Kubernetes, even with QEMU for multi-platform builds. That means: The Gitea Action gets executed in the Docker-in-Docker container, and then the Docker Buildx process connects directly to the Kubernetes API and executes its build job this way. A bit strange, but it works. This is an example of a Gitea Action which builds a multi-platform container image:

name: Build and Push Image
on: [ push ]

    name: Build and push image
    runs-on: ubuntu-latest
    container: catthehacker/ubuntu:act-latest

    - name: Checkout
      uses: actions/checkout@v4

    - name: Create Kubeconfig
      run: |
        mkdir $HOME/.kube
        echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config        

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
        driver: kubernetes
        driver-opts: |

    - name: Login to Docker Registry
      uses: docker/login-action@v3
        registry: git.tbrnt.ch
        username: ${{ secrets.REGISTRY_USERNAME }}
        password: ${{ secrets.REGISTRY_TOKEN }}

    - name: Build and push
      uses: docker/build-push-action@v5
        context: .
        push: true
        platforms: linux/amd64,linux/arm64
        tags: |

In the repository secret, add the Kubeconfig content into KUBECONFIG_BUILDX. I created a service account and a service account token with an appropriate RoleBinding (to the edit ClusterRole).

Oh, and by the way: Gitea also offers an integrated OCI compliant container registry. How cool is that? Now Gitea has everything needed for a modern code flow: Code hosting and collaboration, CI/CD Pipelines and a package / container repository. I don't need more.

The following shell snippet can be used to create a proper Kubeconfig:


ca=$(kubectl -n act-runner get secret/$name -o jsonpath='{.data.ca\.crt}')
token=$(kubectl -n act-runner get secret/$name -o jsonpath='{.data.token}' | base64 --decode)
namespace=$(kubectl -n act-runner get secret/$name -o jsonpath='{.data.namespace}' | base64 --decode)

echo "                                                                                               
apiVersion: v1
kind: Config
- name: default-cluster
    certificate-authority-data: ${ca}
    server: ${server}
- name: default-context
    cluster: default-cluster
    namespace: default
    user: default-user      
current-context: default-context
- name: default-user            
    token: ${token} 
" > sa.kubeconfig

A few interesting facts about Gitea Actions:

As always, my code is open, have a peak here at my real live configuration:

Enjoy The Action!