Ir al contenido

Gitea Actions (CI/CD)

El propio repositorio de RepoFerry usa un workflow de Gitea Actions (.gitea/workflows/build-and-push.yml) que construye y publica automáticamente las tres imágenes Docker cada vez que un commit llega a main — una red de seguridad además del publicado versionado manual de scripts/git/release.sh. Esta guía cubre cómo configurar Gitea Actions en una instancia de Gitea autoalojada para que ese workflow (o el tuyo propio) llegue a ejecutarse de verdad, ya que no hace nada sin un runner registrado.

Gitea Actions no viene activado por defecto. Si ejecutas Gitea con Docker Compose usando las variables de entorno habituales GITEA__seccion__CLAVE, añade:

- GITEA__actions__ENABLED=true

al servicio server, y aplícalo:

Terminal window
docker compose up -d

Sin Docker Compose (o si prefieres editar directamente el fichero de configuración — es también ahí donde acaba realmente aterrizando la variable de entorno de arriba), fíjalo en el propio app.ini de Gitea:

[actions]
ENABLED = true

app.ini suele vivir en /data/gitea/conf/app.ini dentro del contenedor (o donde apunte GITEA_CUSTOM/GITEA_WORK_DIR en una instalación sin Docker). Tras editarlo directamente, reinicia Gitea para que el cambio surta efecto — con Docker Compose basta con docker compose restart server, sin necesidad de recrear el contenedor.

Comprueba que ha surtido efecto:

Terminal window
docker exec <nombre-contenedor-gitea> env | grep GITEA__actions
docker exec <nombre-contenedor-gitea> cat /data/gitea/conf/app.ini | grep -A2 "\[actions\]"

Ambos deberían mostrar ENABLED = true / GITEA__actions__ENABLED=true. También debería aparecer ahora una pestaña Actions en cualquier repositorio, y Administración del sitio → Acciones → Nodos debería cargar sin un 404.

A diferencia de GitHub Actions, Gitea no ofrece ningún runner alojado — nada se ejecuta hasta que registras al menos uno. act_runner (el runner propio de Gitea) necesita acceso a Docker para poder construir y publicar imágenes. Dos formas de ejecutarlo, elige la que mejor encaje con cómo ya gestionas el servidor:

Primero, consigue un token de registro: Administración del sitio → Acciones → Nodos → "Crear nuevo nodo" (en inglés, Site Administration → Actions → Runners → "Create new Runner").

Gitea publica una imagen oficial gitea/act_runner, que encaja de forma natural junto a un stack de Gitea que ya corre en Docker Compose. Si el propio Gitea corre en el mismo proyecto de compose, añade el runner a la misma red para que pueda alcanzar a Gitea directamente por nombre de contenedor en vez de dar la vuelta por el dominio público:

runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: always
networks:
- gitea
environment:
- GITEA_INSTANCE_URL=https://tu-dominio-gitea # la URL pública, no el nombre del contenedor
- GITEA_RUNNER_REGISTRATION_TOKEN=<TOKEN>
- GITEA_RUNNER_NAME=repoferry-runner
- CONFIG_FILE=/config.yaml
volumes:
- ./runner-data:/data
- ./config.yaml:/config.yaml:ro
- /var/run/docker.sock:/var/run/docker.sock # le permite construir/publicar imágenes con el Docker del host

docker compose up -d lo registra automáticamente en el primer arranque (el entrypoint lee esas variables GITEA_RUNNER_*) y lo deja corriendo como cualquier otro servicio — sin necesidad de una unidad systemd aparte. ./runner-data conserva su registro, para que no se registre de nuevo en cada reinicio.

Sigue haciendo falta un config.yaml al lado (ver más abajo) — montado y referenciado vía CONFIG_FILE arriba.

  1. Instala act_runner:

    Terminal window
    curl -o act_runner -L https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
    chmod +x act_runner
    sudo mv act_runner /usr/local/bin/act_runner
  2. Genera una configuración:

    Terminal window
    act_runner generate-config > config.yaml
  3. Regístralo:

    Terminal window
    act_runner register --instance https://tu-instancia-gitea --token <TOKEN> --name mi-runner
  4. Ejecútalo como servicio (systemd, para que sobreviva a reinicios) en vez de dejar act_runner daemon corriendo en una terminal.

config.yaml: tres ajustes que de verdad importan

Sección titulada «config.yaml: tres ajustes que de verdad importan»

Elijas la opción que elijas, edita config.yaml:

capacity: 3
container:
network: "<nombre-real-de-tu-red-docker>"
options: --cpus=2 --memory=4g
  • container.network: los contenedores de job que crea act_runner necesitan estar en la misma red en la que corre el propio Gitea para resolver su hostname interno durante el actions/checkout — que el contenedor del runner esté en esa red no basta, porque cada job corre en su propio contenedor aparte. Averigua el nombre real con docker network ls — Compose le añade el prefijo del proyecto, así que probablemente no sea simplemente "gitea" (en nuestro caso resultó ser "gitea_gitea"). Sin esto, el checkout falla con Could not resolve host: <nombre>.
  • container.options: flags extra pasados a docker create para cada contenedor de job — buen sitio para límites de recursos (--cpus/--memory). No añadas aquí -v /var/run/docker.sock:/var/run/docker.sockact_runner ya lo monta automáticamente; repetirlo falla con Duplicate mount point.
  • capacity: cuántos jobs ejecuta el runner a la vez (por defecto 1, es decir, uno detrás de otro). Si tu workflow usa una matrix strategy para construir varias cosas en paralelo (como el propio build-and-push.yml de RepoFerry, un job por imagen), sube esto al menos al tamaño de la matrix, o los jobs se quedan igualmente encolados en el mismo runner. Como los límites de container.options se aplican por job concurrente, capacity: 3 con --cpus=2 cada uno implica que el host puede necesitar hasta 6 CPUs libres a la vez.

La sintaxis exacta puede variar entre versiones de act_runner — consulta la documentación oficial de Gitea Actions si algo no coincide con lo que ves. Reinicia el runner tras editar este fichero (docker compose restart runner, o reinicia el servicio systemd) — un simple docker compose up -d no recoge cambios en el contenido de un fichero montado como bind mount.

Comprueba que está activo de verdad: Administración del sitio → Acciones → Nodos lo lista con un estado y la última hora en línea. El estado puede aparecer como “Inactivo” incluso estando todo perfectamente bien — esa etiqueta significa “conectado, esperando un job”, no “desconectado”. Lo que de verdad importa es la última hora en línea: si se sigue actualizando a “ahora” cada vez que refrescas la página, el runner está conectado y sano, sin importar lo que diga la etiqueta de estado. Si esa hora se queda congelada en el pasado, eso sí es la señal real de que algo falla. También puedes mirar los logs del propio runner directamente:

Terminal window
docker compose logs -f runner # Opción A
journalctl -u act_runner -f # Opción B, si corre como servicio systemd

Un runner cuya “última hora en línea” nunca se actualiza casi siempre significa que no puede alcanzar GITEA_INSTANCE_URL — repasa la URL y que ambos contenedores estén en la misma red de Docker (Opción A), o el valor de --instance (Opción B).

El propio .gitea/workflows/build-and-push.yml de RepoFerry es un ejemplo funcional: en cada push a main, inicia sesión en Docker Hub, lee la versión directamente de backend/build.gradle (no del tag v<version>, que se empuja por separado y podría no existir todavía cuando arranca el workflow), y ejecuta scripts/docker/build-and-push.sh <version> para construir y publicar las tres imágenes etiquetadas tanto :latest como :<version>. También se dispara en push a develop, publicando las mismas tres imágenes etiquetadas :beta en su lugar — pensado para un entorno de staging que siga siempre la punta de desarrollo. :latest nunca se sobrescribe con un push a develop: build-and-push.sh solo reetiqueta :latest cuando el tag recibido es una versión SemVer real (X.Y.Z), nunca para beta ni ningún otro tag arbitrario.

Necesita dos secretos, configurados en Settings del repositorio → Actions → Secrets — nunca como valores literales en el fichero del workflow ni en ningún otro sitio del repositorio:

  • DOCKERHUB_USERNAME — tu usuario de Docker Hub.
  • DOCKERHUB_TOKEN — un access token de Docker Hub, no la contraseña de tu cuenta: menú de usuario → Account Settings → Personal access tokens → Generate new token. Ponle una descripción, una fecha de caducidad, y fija Access permissions a Read & Write (necesario para poder publicar imágenes), luego Generate.

Los tokens de registro, los tokens de runner y los dos secretos de arriba son todos credenciales — trátalos exactamente igual que cualquier otro secreto: nunca los commitees, nunca los pegues en un issue/PR/mensaje de commit, y rótalos si sospechas que se han filtrado.