Part 2 of 5

    CI/CD on Gitea: Gitea Actions & Woodpecker CI

    Two excellent options for CI/CD: Gitea Actions (GitHub Actions-compatible) or Woodpecker CI (container-first with advanced pipeline features).

    Prerequisites: Complete Part 1 (Gitea Setup) before following this guide. You need a working Gitea instance accessible at your domain or IP.

    Recommendation: Start with Gitea Actions if you are migrating from GitHub. Use Woodpecker if you need matrix builds, advanced caching, or per-step resource limits.

    Gitea Actions vs Woodpecker CI

    FeatureGitea ActionsWoodpecker CI
    GitHub Actions syntaxYes (compatible)No (own YAML format)
    Built into GiteaYesNo (separate service)
    Docker-native runnersVia act_runnerNative
    Matrix buildsLimitedFull support
    Step-level cachingBasicAdvanced
    Plugin ecosystemGitHub Actions marketplaceWoodpecker plugins
    Resource limits per stepNoYes
    Best forGitHub migrantsPower users / larger teams

    Option A: Gitea Actions

    Gitea Actions uses a separate act_runner process that polls your Gitea instance for queued jobs. Runners can be global (all repos), organization-level, or repository-level. The runner executes each workflow step inside Docker containers.

    1

    Enable Gitea Actions

    Gitea Actions is disabled by default. Enable it in your app.ini configuration:

    Enable Actions in app.ini
    # Edit /opt/gitea/config/app.ini
    # If using Docker, the file is mounted at ./config/app.ini
    
    [actions]
    ENABLED = true
    DEFAULT_ACTIONS_URL = https://gitea.com
    
    # Restart Gitea to apply
    cd /opt/gitea && docker compose restart gitea
    2

    Install act_runner

    The act_runner is a standalone binary that connects to your Gitea instance and executes workflow jobs:

    Download and install act_runner
    # Download the latest act_runner binary
    wget https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
    chmod +x act_runner-0.2.11-linux-amd64
    sudo mv act_runner-0.2.11-linux-amd64 /usr/local/bin/act_runner
    
    # Verify
    act_runner --version
    3

    Register the Runner

    Generate a runner registration token in Gitea (Admin Panel → Actions → Runners → Create Runner Token), then register:

    Register act_runner
    # Create a config directory
    mkdir -p /opt/act_runner && cd /opt/act_runner
    
    # Register the runner (interactive prompt)
    act_runner register
    # When prompted:
    # Gitea instance URL: https://git.yourdomain.com
    # Runner token: (paste token from Gitea UI)
    # Runner name: ramnode-runner-01
    # Runner labels: ubuntu-22.04:docker://node:20 (comma-separated)
    
    # Start the runner
    act_runner daemon
    4

    Run act_runner as a Systemd Service

    Create a systemd service for persistent operation:

    /etc/systemd/system/act_runner.service
    [Unit]
    Description=Gitea Actions Runner
    After=network.target
    
    [Service]
    WorkingDirectory=/opt/act_runner
    ExecStart=/usr/local/bin/act_runner daemon --config /opt/act_runner/.runner
    Restart=always
    User=root
    
    [Install]
    WantedBy=multi-user.target
    Enable and start the service
    sudo systemctl daemon-reload
    sudo systemctl enable --now act_runner
    sudo systemctl status act_runner
    5

    Your First Gitea Actions Workflow

    Create a .gitea/workflows/ci.yml file in any repository. The syntax is identical to GitHub Actions:

    .gitea/workflows/ci.yml
    name: CI Pipeline
    
    on:
      push:
        branches: [main, develop]
      pull_request:
        branches: [main]
    
    jobs:
      test:
        runs-on: ubuntu-22.04
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Set up Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '20'
              cache: 'npm'
    
          - name: Install dependencies
            run: npm ci
    
          - name: Run tests
            run: npm test
    
          - name: Build Docker image
            run: |
              docker build -t myapp:${{ gitea.sha }} .

    GitHub Actions Compatibility: Most actions from the GitHub Actions marketplace work in Gitea Actions when you set DEFAULT_ACTIONS_URL = https://gitea.com or point to a mirror. actions/checkout, actions/setup-node, and docker/* actions all work without modification.

    Option B: Woodpecker CI

    Woodpecker CI consists of two components: a server that manages pipelines and a UI, and one or more agents that actually execute pipeline steps. Agents are stateless and can be scaled horizontally.

    6

    Create a Gitea OAuth Application

    Woodpecker authenticates with Gitea via OAuth:

    • 1. In Gitea: User Settings → Applications → OAuth2 Applications
    • 2. Application Name: Woodpecker CI
    • 3. Redirect URI: https://ci.yourdomain.com/authorize
    • 4. Save the Client ID and Client Secret for the next step
    7

    Deploy Woodpecker with Docker Compose

    /opt/woodpecker/docker-compose.yml
    version: '3.8'
    
    services:
      woodpecker-server:
        image: woodpeckerci/woodpecker-server:latest
        container_name: woodpecker-server
        restart: unless-stopped
        ports:
          - '8000:8000'
        environment:
          - WOODPECKER_OPEN=false
          - WOODPECKER_HOST=https://ci.yourdomain.com
          - WOODPECKER_GITEA=true
          - WOODPECKER_GITEA_URL=https://git.yourdomain.com
          - WOODPECKER_GITEA_CLIENT=YOUR_CLIENT_ID
          - WOODPECKER_GITEA_SECRET=YOUR_CLIENT_SECRET
          - WOODPECKER_AGENT_SECRET=generate_with_openssl_rand_hex_32
          - WOODPECKER_DATABASE_DRIVER=sqlite3
          - WOODPECKER_DATABASE_DATASOURCE=/var/lib/woodpecker/woodpecker.db
        volumes:
          - woodpecker_data:/var/lib/woodpecker
    
      woodpecker-agent:
        image: woodpeckerci/woodpecker-agent:latest
        container_name: woodpecker-agent
        restart: unless-stopped
        depends_on: [woodpecker-server]
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
        environment:
          - WOODPECKER_SERVER=woodpecker-server:9000
          - WOODPECKER_AGENT_SECRET=generate_with_openssl_rand_hex_32
          - WOODPECKER_MAX_WORKFLOWS=4
    
    volumes:
      woodpecker_data:
    Start Woodpecker
    cd /opt/woodpecker && docker compose up -d
    docker compose logs -f woodpecker-server

    Security note: Mounting /var/run/docker.sock gives the CI runner full access to your host Docker daemon. For shared or multi-tenant environments, consider using rootless Podman or Kaniko instead. For a single-developer RamNode VPS, the socket mount is acceptable.

    8

    Your First Woodpecker Pipeline

    Create a .woodpecker.yml file in your repository root. Woodpecker's pipeline format is step-based and container-native:

    .woodpecker.yml
    pipeline:
      install:
        image: node:20-alpine
        commands:
          - npm ci
    
      test:
        image: node:20-alpine
        commands:
          - npm test
        depends_on: [install]
    
      build-image:
        image: plugins/docker
        settings:
          repo: registry.yourdomain.com/myapp
          tags:
            - latest
            - ${CI_COMMIT_SHA:0:8}
          username:
            from_secret: docker_username
          password:
            from_secret: docker_password
        depends_on: [test]
        when:
          branch: main

    Shared Configuration

    Docker-in-Docker: Building Container Images

    Both Gitea Actions and Woodpecker can build Docker images during CI. Build and push to Gitea's built-in container registry:

    Gitea Actions — Build and push
    # In your CI workflow (Gitea Actions syntax)
    - name: Build and push Docker image
      run: |
        echo ${{ secrets.GITEA_TOKEN }} | docker login git.yourdomain.com -u ${{ gitea.actor }} --password-stdin
        docker build -t git.yourdomain.com/your-org/myapp:${{ gitea.sha }} .
        docker push git.yourdomain.com/your-org/myapp:${{ gitea.sha }}
        docker tag git.yourdomain.com/your-org/myapp:${{ gitea.sha }} git.yourdomain.com/your-org/myapp:latest
        docker push git.yourdomain.com/your-org/myapp:latest

    Managing Secrets

    Repository Secrets in Gitea Actions

    Add secrets in the repository UI under Settings → Secrets and Variables → Actions. Reference them as ${{ secrets.MY_SECRET }} in your workflow YAML. Secrets are masked in logs automatically.

    Organization-Level Secrets

    For secrets shared across multiple repositories, use Organization Settings → Secrets. These are available to all repos in the org without duplicating configuration.

    Woodpecker Secrets

    Woodpecker stores secrets in its own database. Add them via the UI or CLI:

    Woodpecker CLI — Add secrets
    # Using Woodpecker CLI
    woodpecker-cli secret add --repository your-org/your-repo \
      --name docker_password \
      --value 'your_registry_password'
    
    # Or organization-wide secrets
    woodpecker-cli secret add --organization your-org \
      --name deploy_key \
      --value @/path/to/private_key.pem

    Pipeline Caching Strategies

    npm / yarn Cache with Gitea Actions

    Cache node_modules
    - name: Cache node_modules
      uses: actions/cache@v3
      with:
        path: ~/.npm
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-node-

    Docker Layer Cache

    Docker Buildx with cache
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Build with cache
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: git.yourdomain.com/org/app:latest
        cache-from: type=registry,ref=git.yourdomain.com/org/app:buildcache
        cache-to: type=registry,ref=git.yourdomain.com/org/app:buildcache,mode=max

    Notifications & Status Badges

    Status Badge in README

    Status badges
    # For Gitea Actions, add to README.md:
    [![CI Status](https://git.yourdomain.com/your-org/your-repo/actions/workflows/ci.yml/badge.svg)](https://git.yourdomain.com/your-org/your-repo/actions)
    
    # For Woodpecker CI:
    [![Pipeline Status](https://ci.yourdomain.com/api/badges/your-org/your-repo/status.svg)](https://ci.yourdomain.com/your-org/your-repo)

    Pipeline Failure Notifications (Woodpecker)

    Slack notification step
    # Woodpecker: Notify via webhook on failure
    notify:
      image: plugins/slack
      settings:
        webhook:
          from_secret: slack_webhook
        channel: dev-alerts
        template: >
          {{#success build.status}}Build succeeded for {{repo.name}}{{else}}Build FAILED for {{repo.name}} on {{build.branch}}{{/success}}
      when:
        status: [failure, success]

    What's Next

    Part 3 covers setting up Gitea's built-in package registries for Docker images, npm packages, and PyPI packages — all hosted on your RamNode VPS without any third-party registry fees.