Documentation at Gitea: https://docs.gitea.com/usage/actions/overview
Gitea Actions is a CI/CD Automation tool, like (and compatible with) Github Actions. Gitea Actions uses Act Runner as its medium of running the desired workflows. It does this, by creating one or more containers that the jobs run inside of. When a job is done, the container is deleted again, and the runner is now ready for the next job.
Gitea Actions Act runner, can either be installed directly on a host computer (this can be the same hosts that Gitea is installed on, or a different one), or run in docker containers.
According to the documentation, it might be possible to make it work with other OCI runtimes (like Podman), but it is untested, and unsupported.
Since I am running Gitea in a docker container, this is the installation method I will focus on. Installing directly on a host machine is covered in the official documentation linked above.
docker ps on gitea server:
[gitea@gitea gitea]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e01bcefd5070 gitea/act_runner:nightly "/sbin/tini -- /opt/…" 4 days ago Up 3 seconds gitea-runner-1
340be409d93e gitea/gitea:1.22.3 "/usr/bin/entrypoint…" 4 days ago Up 38 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, 0.0.0.0:2222->22/tcp, [::]:2222->22/tcp gitea
1fd88a131639 postgres:14 "docker-entrypoint.s…" 4 days ago Up 38 minutes 5432/tcp gitea-db-1
A runner can be registered at 3 different levels on a Gitea server. This can be used to control who can use them, and on which repositories they can be used.
There are many use cases for a tool that can automate predifined workflows. Amongst these are for example:
Many many more uses for a tool like this exists. For inspiration, we can take a look at the Awesome Actions Repository.
Runners can either be defined in their own docker compose files, or in the docker compose file we use to run Gitea.
With both ways to run a runner, it is important that the docker network we use is set to external: true
networks:
gitea:
external: true
When a network is set to external, we loose the ability for docker compose to create the network for us, so it needs to be created with the docker networks create command. This only needs to be done the first time.
[gitea@gitea gitea]$ docker network create gitea
a7391fec97daa796538321bb9631622e7cc9bf11113aa27e867400a52a0e31e3
If we build the runner in its own compose file, it could look like this:
---
networks:
gitea:
external: true
services:
runner:
image: gitea/act_runner:nightly
environment:
CONFIG_FILE: /data/config.yaml
GITEA_INSTANCE_URL: "https://url/of/your/gitea/instance"
GITEA_RUNNER_REGISTRATION_TOKEN: "${RUNNER_REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "test-runner"
networks:
- gitea
volumes:
- ./runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
Since a runner like this is not worth much without gitea running. it can make sense to define it in the same docker compose file as gitea itself. This gives we the benefit of using the depends_on key to make sure the runner won’t start up before gitea is running (and then fail…).
---
networks:
gitea:
external: true
services:
server:
image: docker.io/gitea/gitea:1.22.3
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST="${GITEA_DB_PORT}"
- GITEA__database__NAME=gitea
- GITEA__database__USER="${GITEA_DB_USERNAME}"
- GITEA__database__PASSWD="${GITEA_DB_PASSWD}"
restart: always
networks:
- gitea
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
depends_on:
- db
db:
image: docker.io/postgres:14
restart: always
environment:
- POSTGRES_USER="${GITEA_DB_USERNAME}"
- POSTGRES_PASSWORD="${GITEA_DB_PASSWD}"
- POSTGRES_DB=gitea
networks:
- gitea
volumes:
- ./postgres:/var/lib/postgresql/data
runner:
image: docker.io/gitea/act_runner:0.2.11
environment:
CONFIG_FILE: /data/config.yaml
GITEA_INSTANCE_URL: "https://url/of/your/gitea/instance"
GITEA_RUNNER_REGISTRATION_TOKEN: "${RUNNER_REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "test-runner"
#GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
networks:
- gitea
depends_on:
- server
- db
volumes:
- ./runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
This docker compose file contains variables, like "${GITEA_DB_USERNAME}". This is a way of handling secrets and sensitive information.
As mentioned above, the docker compose file would without these secrets variables, contain sensitive information, like usernames, passwords, and tokens. Since this is very insecure, we use variables, that we put in a secrets file or a file called .env (docker compose will automatically ready a .env file in the same directory as the compose file itself).
It is VERY important that this secrets or .env file NEVER gets pushed to a git repository! To make sure git ignores these files, we can create a .gitignore file in the root of our git repository, that tells git which files to ignore.
# Ignore all files in this folder or sub folder with the word secrets in the file name.
*/*secrets*
# Ignore all files in this folder or sub folder called .env
*/.env
Once the ignore rules are in place, we can create the .env file next to our docker compose file, on the server where we want to run our runner container. An .env file for the above docker compose file could look like this (Remember to change the values):
# Gitea and Postgres variables
GITEA_DB_PORT: db:5432
GITEA_DB_USERNAME: changeMeToSomethingElse
GITEA_DB_PASSWD: changeMeToSomethingElse
# Runner Variable <---- This variable is found in the Gitea UI
RUNNER_REGISTRATION_TOKEN: Ag2ZJPdYP24divikTS8LzGr0dg9SYY0BgmFLP5kj .
Where we get the Runner Registration Token, depends on which level, the runner has to run.
Once a runner is started, we will be able to see it idle and ready in the Settings > Actions > Runners of the level we choose for the runner.
We can have more runners if we want. Each runner needs to have its own persistent data directory, and its own config file. This also enables us to define the available images (labels) per runner. When a runner is started for the first time, and it registeres itself with the Gitea server, it creates a .runner file next to its config file. This file contains identifying information about the runner, in json format. The .runner file can look like this
{
"WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
"id": 1,
"uuid": "31712856-5dc3-4db7-b3d0-642a38c09d19",
"name": "DragonflightRunner",
"token": "f389e436b53b4b8041955a140b56ab32aed1f",
"address": "https://url/of/your/gitea/instance",
"labels": [
"ubuntu-latest:docker://gitea/runner-images:ubuntu-latest",
"ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04",
"ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04",
"alma9-latest:docker://docker.io/alma9:latest",
"alpine-latest:docker://docker.io/library/alpine:latest"
]
}
Should we at any point need to delete and reestablish the runner, this file needs to be deleted first. We also need to pull a new token from the Gitea UI and add to the docker compose file.
If an action has more than one runner available to it, it will use them in the following order: Repository Runners > Organization Runners > Instance Runners.
Once we set up a runner for the first time it has some predefined images (called labels) based on Ubuntu set in its config file.
If we are satisfied with these, we can just leave them as is. However if we want to use different container images, we have the option of just finding some prebuilt images on an container image registry (like quay.io, docker.io, or registry.redhat.io), or build an image ourselves with a container file (and docker build or podman build) and then host it in a container registry where the runner can pull it from.
To add images to our runner, we need to edit the runners configuration file, and find the section called “runner:”. In this section, at the end of the section, we will find the labels.
runner:
...
omited output
...
labels:
- "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
- "ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04"
- "ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04"
To make a new image available to our runner, we need to add a new line in the labels section, formatted like this:
- "[image-label]:docker://[repository]/[image]:[tag]
So to add Alma Linux 9 and the latest Alpine Linux as available images, we would make our labels section look like this
runner:
...
omited output
...
labels:
- "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
- "ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04"
- "ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04"
- "alma9-latest:docker://docker.io/alma9:latest"
- "alpine-latest:docker://docker.io/library/alpine:latest"
The final step to making new images available to the runner, is to restart the runner container. To do this we can navigate to the location of our docker compose file, and use the docker compose command to restart the runners container.
[gitea@gitea gitea]$ docker compose restart runner
[+] Restarting 1/1
✔ Container gitea-runner-1 Started
If we now look at our runner again in the Gitea UI, we should see our new images available to the runner.
To use Gitea Actions on our repository, we need to add a workflow file to the repository with instructions for what we want to runner to do.
Workflow files are written in .yaml format, and have to be placed in a specific location in our repository: repositoryRoot/.gitea/workflows/workflowfile.yml. The file name can be whatever we want.
A workflow file can look like this (example taken from Giteas own documentation):
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
In this example, we are triggering the workflow by pushing to the repository, but there are several other ways to trigger the workflow. For more inspiration on how to write these runner workflow files, we can look to Githubs workflow documentation .
If we want our Gitea Actions workflows to be useful for more than just a fancy test of the runner, we need to have it be able to access other ressources in our environment. This will sometimes require login information, that we do not want to leave in clear text in our workflow files.
Gitea Actions (and other CI/CD systems i guess), have a an ace up their sleeves for helping us with handling sensitive information. If we navigate to Settings > Actions > Secrets in the level we our runner is registered, we can register secrets, that we can reference in our workflow files.
Some good suggestions information we might want to keep secret could be:
If we zoom in on the SSH_KEY secret, it could be filled out like this:
Once we have saved our secrets, we will be able to see them in the Gitea UI, but we will not be able to see their contents any longer, as this is stored encrypted in Gitea. If we at any point need to edit a secret, we need to delete it, and create it again.
We can now reference our secrets in our workflow files, with this variable: ${{ secrets.variablename }} This way we can use the contents of our secrets, without exposing them in clear text.
This article was pushed built, and pushed automatically using Gitea Actions with the following workflow file:
name: Hugo Building by Gitea Actions
run-name: Hugo Building by Gitea Actions
on:
push:
branches:
- main
jobs:
Build website with hugo and push it:
runs-on: ubuntu-22.04
steps:
- name: Update apt cache
run: apt update
- name: Install Hugo and rsync
run: apt install -y hugo rsync
- name: configure SSH
run: |
mkdir -p ~/.ssh/
chmod 0700 ~/.ssh
echo "$SSH_KEY" > ~/.ssh/dragonflightkey
chmod 0600 ~/.ssh/dragonflightkey
cat >> ~/.ssh/config << END
Host dragonflightvps
Hostname $SSH_HOST
User $SSH_USER
IdentityFile ~/.ssh/dragonflightkey
StrictHostKeyChecking no
END
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
- name: Check out latest repository code
uses: actions/checkout@v4
- name: Build Website with Hugo
run: hugo -d /tmp/website
- name: copy new website content over with rsync
run: rsync -avz --delete /tmp/website dragonflightvps:/opt/podmnts/apache/DragonflightWeb/
- name: Restart apache container
run: ssh dragonflightvps systemctl --user restart httpd-quadlet.service