Woodpecker CI es un sistema de integración continua autoalojado en el que cada paso de un pipeline se ejecuta dentro de un contenedor. Se compone de un servidor (interfaz web, API y cola de trabajos) y de uno o varios agentes que ejecutan los pipelines. En este tutorial instalarás el servidor y un agente con Docker Compose en Ubuntu 24.04, los conectarás a una instancia de Gitea o Forgejo mediante OAuth2, publicarás la interfaz detrás de Nginx con HTTPS y ejecutarás un pipeline de prueba.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS y al menos 2 GB de RAM, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker.
  • Nginx y Certbot instalados (sudo apt install nginx certbot python3-certbot-nginx).
  • Una instancia de Gitea o Forgejo accesible por HTTPS en la que tengas una cuenta, por ejemplo https://git.your_domain.
  • Un subdominio para Woodpecker, como ci.your_domain, con un registro DNS A apuntando a la IP del servidor.
  • Los puertos 80 y 443 abiertos en el cortafuegos.

A lo largo de la guía, sustituye ci.your_domain y git.your_domain por tus dominios reales y your_gitea_user por tu nombre de usuario en Gitea o Forgejo.

Paso 1: Crear la aplicación OAuth2 en Gitea o Forgejo

Woodpecker no tiene usuarios propios: los usuarios inician sesión con su cuenta de Gitea o Forgejo y Woodpecker usa ese acceso para leer repositorios y registrar webhooks. Para ello necesitas una aplicación OAuth2.

En la interfaz web de Gitea o Forgejo:

  1. Abre tu avatar y ve a Configuración > Aplicaciones.
  2. En la sección Administrar aplicaciones OAuth2, escribe Woodpecker CI como nombre de la aplicación.
  3. En URI de redirección, introduce https://ci.your_domain/authorize.
  4. Pulsa Crear aplicación.

Gitea mostrará un ID de cliente y un Secreto de cliente. Cópialos ahora, porque el secreto no se vuelve a mostrar.

Paso 2: Preparar el directorio y los secretos

Crea un directorio para la instalación y entra en él:

sudo mkdir -p /opt/woodpecker
cd /opt/woodpecker

El servidor y los agentes se autentican entre sí con un secreto compartido. Genera uno aleatorio:

openssl rand -hex 32
3f9c1b0d6e2a4c7f8b5e9d1a2c3b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d

Guarda los valores sensibles en un fichero .env, que Docker Compose lee automáticamente. Así no quedan escritos en el docker-compose.yml:

sudo nano /opt/woodpecker/.env
WOODPECKER_HOST=https://ci.your_domain
WOODPECKER_ADMIN=your_gitea_user
WOODPECKER_AGENT_SECRET=your_agent_secret
WOODPECKER_GITEA_URL=https://git.your_domain
WOODPECKER_GITEA_CLIENT=your_oauth_client_id
WOODPECKER_GITEA_SECRET=your_oauth_client_secret

Sustituye your_agent_secret por el valor generado con openssl y los dos últimos por el ID y el secreto de cliente del paso 1. Restringe los permisos del fichero:

sudo chmod 600 /opt/woodpecker/.env

Paso 3: Definir el servidor y el agente con Docker Compose

Crea el fichero de Compose:

sudo nano /opt/woodpecker/docker-compose.yml
services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:v3
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"
    volumes:
      - woodpecker-server-data:/var/lib/woodpecker/
    environment:
      - WOODPECKER_HOST=${WOODPECKER_HOST}
      - WOODPECKER_OPEN=false
      - WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
      - WOODPECKER_GITEA=true
      - WOODPECKER_GITEA_URL=${WOODPECKER_GITEA_URL}
      - WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
      - WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}

  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:v3
    command: agent
    restart: unless-stopped
    depends_on:
      - woodpecker-server
    volumes:
      - woodpecker-agent-config:/etc/woodpecker
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WOODPECKER_SERVER=woodpecker-server:9000
      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
      - WOODPECKER_MAX_WORKFLOWS=2

volumes:
  woodpecker-server-data:
  woodpecker-agent-config:

Algunos detalles de esta configuración:

  • El puerto web 8000 solo escucha en 127.0.0.1, porque el acceso público pasará por Nginx con HTTPS.
  • El agente llega al servidor por la red interna de Compose (woodpecker-server:9000), así que el puerto gRPC 9000 no se publica.
  • WOODPECKER_OPEN=false impide que cualquier usuario de tu Gitea se registre en Woodpecker. WOODPECKER_ADMIN concede permisos de administrador a tu usuario.
  • El servidor usa SQLite en el volumen woodpecker-server-data, suficiente para un equipo pequeño.
  • WOODPECKER_MAX_WORKFLOWS limita cuántos workflows ejecuta el agente en paralelo. Ajústalo a la CPU y RAM del servidor.

Arranca los contenedores:

cd /opt/woodpecker
sudo docker compose up -d

Comprueba que ambos servicios están en marcha:

sudo docker compose ps
NAME                                IMAGE                               STATUS
woodpecker-woodpecker-agent-1       woodpeckerci/woodpecker-agent:v3    Up 10 seconds
woodpecker-woodpecker-server-1      woodpeckerci/woodpecker-server:v3   Up 11 seconds

Revisa los logs del agente para confirmar que se ha conectado al servidor. No debe haber errores de autenticación repetidos:

sudo docker compose logs woodpecker-agent --tail 20

Paso 4: Publicar Woodpecker con Nginx y HTTPS

Gitea solo redirigirá a la URL registrada en la aplicación OAuth2, así que Woodpecker debe responder en https://ci.your_domain. Crea un bloque de servidor de Nginx:

sudo nano /etc/nginx/sites-available/woodpecker
server {
    listen 80;
    listen [::]:80;
    server_name ci.your_domain;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}

proxy_buffering off y el proxy_read_timeout largo permiten que los logs de los pipelines se muestren en tiempo real en el navegador.

Activa el sitio, valida la sintaxis y recarga Nginx:

sudo ln -s /etc/nginx/sites-available/woodpecker /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Si usas UFW, permite el tráfico web:

sudo ufw allow 'Nginx Full'

Obtén el certificado de Let's Encrypt. Certbot modificará el bloque de servidor para escuchar en el puerto 443 y redirigir HTTP a HTTPS:

sudo certbot --nginx -d ci.your_domain

Verifica que Woodpecker responde por HTTPS:

curl -sI https://ci.your_domain | head -n 1
HTTP/2 200

Paso 5: Iniciar sesión y activar un repositorio

Abre https://ci.your_domain en el navegador y pulsa Login. Serás redirigido a Gitea, que te pedirá autorizar la aplicación Woodpecker CI. Tras aceptar, volverás a Woodpecker con la sesión iniciada.

Para que Woodpecker ejecute pipelines de un repositorio:

  1. Pulsa Add repository (o el botón +) en la lista de repositorios.
  2. Elige el repositorio y pulsa Enable.

Woodpecker registrará automáticamente un webhook en Gitea. Puedes comprobarlo en Gitea, en Configuración del repositorio > Webhooks, donde aparecerá una entrada apuntando a https://ci.your_domain/api/hook.

Paso 6: Crear el primer pipeline

Woodpecker busca los pipelines en el fichero .woodpecker.yaml de la raíz del repositorio o en ficheros .yaml dentro del directorio .woodpecker/. Cada fichero del directorio se ejecuta como un workflow independiente.

En tu repositorio, crea .woodpecker/ci.yaml con este contenido:

when:
  - event: [push, pull_request]

steps:
  - name: info
    image: alpine:3.20
    commands:
      - echo "Repositorio ${CI_REPO}, rama ${CI_COMMIT_BRANCH}"
      - uname -a

  - name: test
    image: node:22-alpine
    commands:
      - node --version
      - echo "console.log('Pipeline OK')" > check.js
      - node check.js

Cada paso se ejecuta en un contenedor nuevo de la imagen indicada, con el código del repositorio montado como directorio de trabajo, y los pasos se ejecutan en orden. Variables como CI_REPO o CI_COMMIT_BRANCH las inyecta Woodpecker automáticamente.

Haz commit y push del fichero:

git add .woodpecker/ci.yaml
git commit -m "Añadir pipeline de Woodpecker"
git push

En la interfaz de Woodpecker aparecerá un pipeline nuevo. Al abrirlo verás los logs de cada paso y, si todo va bien, los dos pasos en verde, con la salida Pipeline OK en el paso test.

Paso 7: Usar secretos en los pipelines

Las credenciales (tokens de un registro de contenedores, claves de despliegue) no deben ir en el YAML. Woodpecker las guarda cifradas y las inyecta como variables de entorno.

Para crear un secreto, abre el repositorio en Woodpecker, ve a Settings > Secrets y pulsa Add secret. Por ejemplo, crea uno llamado deploy_token con el valor del token.

Referéncialo en un paso con from_secret:

steps:
  - name: deploy
    image: alpine:3.20
    environment:
      DEPLOY_TOKEN:
        from_secret: deploy_token
    commands:
      - test -n "$DEPLOY_TOKEN" && echo "Token disponible"
    when:
      - event: push
        branch: main

Woodpecker oculta el valor de los secretos en los logs. La condición when hace que este paso solo se ejecute en los push a main, no en las pull requests, que es donde conviene limitar el acceso a credenciales de despliegue.

Paso 8: Añadir agentes en otros servidores (opcional)

Si necesitas más capacidad de ejecución, puedes instalar agentes en otros servidores con Docker. En ese caso el agente se conecta al servidor por el puerto gRPC 9000, así que debes publicarlo en el docker-compose.yml del servidor añadiendo - "9000:9000" a la lista ports de woodpecker-server y reiniciando con sudo docker compose up -d.

Limita el acceso a ese puerto a la IP del agente, porque la conexión gRPC no va cifrada con esta configuración:

sudo ufw allow from agent_server_ip to any port 9000 proto tcp

En el servidor del agente, crea /opt/woodpecker-agent/docker-compose.yml:

services:
  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:v3
    command: agent
    restart: unless-stopped
    volumes:
      - woodpecker-agent-config:/etc/woodpecker
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WOODPECKER_SERVER=woodpecker_server_ip:9000
      - WOODPECKER_AGENT_SECRET=your_agent_secret
      - WOODPECKER_MAX_WORKFLOWS=2

volumes:
  woodpecker-agent-config:

Arráncalo con sudo docker compose up -d desde ese directorio. El nuevo agente aparecerá en la interfaz de Woodpecker, en Settings > Agents del panel de administración.

Solución de problemas

Gitea muestra un error de URI de redirección no válida. La URI registrada en la aplicación OAuth2 debe coincidir exactamente con WOODPECKER_HOST seguido de /authorize, incluido el esquema https:// y sin barra final en WOODPECKER_HOST. Corrige la que no coincida y, si cambias el .env, recrea el servidor con sudo docker compose up -d.

El pipeline se queda en estado pendiente. Significa que ningún agente ha tomado el trabajo. Revisa los logs del agente:

sudo docker compose logs woodpecker-agent --tail 50

Los errores de autenticación indican que WOODPECKER_AGENT_SECRET no coincide entre servidor y agente. Los errores de conexión en un agente remoto suelen deberse al puerto 9000 cerrado.

Los pushes no lanzan pipelines. Abre en Gitea el webhook del repositorio y revisa las entregas recientes. Si Gitea no puede alcanzar https://ci.your_domain/api/hook, comprueba el DNS y el certificado. Si Gitea tiene activada la lista de hosts permitidos para webhooks (ALLOWED_HOST_LIST en la sección [webhook] de su app.ini), añade el dominio de Woodpecker.

Conclusión

Tienes Woodpecker CI funcionando con Docker Compose, integrado con Gitea o Forgejo mediante OAuth2, publicado con HTTPS y ejecutando pipelines definidos en .woodpecker/. Como siguientes pasos, puedes migrar la base de datos de SQLite a PostgreSQL cuando crezca el número de usuarios, construir y publicar imágenes con el plugin docker-buildx de Woodpecker, o añadir agentes en otros servidores para repartir la carga.