Gitea Actions Container Builds

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
        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='{\.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!

You've successfully subscribed to Tobias Brunner aka tobru
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.