Gitea Actions: Build Your Own GitHub-Like CI/CD Pipeline Without the Price Tag

Gitea Actions: Build Your Own GitHub-Like CI/CD Pipeline Without the Price Tag

GitHub Actions changed how teams think about CI/CD. But at $4 per user per month (and that's just for the Team plan), the costs add up fast. For a 20-person team, you're looking at $960/year before you even run a single pipeline.

Here's what most people don't know: Gitea added Actions support in version 1.19, and it's almost entirely compatible with GitHub Actions workflows. Same YAML syntax, same workflow structure, same marketplace actions (mostly). You can literally copy your .github/workflows files and run them on your own infrastructure.

What You'll Build

By the end of this guide, you'll have:

  • A self-hosted Gitea instance with Actions enabled
  • A working CI/CD pipeline that builds, tests, and deploys
  • Runner infrastructure that scales with your team

Enabling Actions in Gitea

First, you need Gitea 1.19 or later. If you're running on Elestio, you're already on a recent version with Actions ready to enable.

Edit your app.ini configuration:

[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = github

The DEFAULT_ACTIONS_URL = github setting lets you use actions from GitHub's marketplace directly. Restart Gitea after making changes.

Setting Up a Runner

Actions need somewhere to execute. Gitea uses the act_runner binary, which you can run on any machine with Docker installed.

# Download the runner
wget https://gitea.com/gitea/act_runner/releases/download/v0.2.6/act_runner-0.2.6-linux-amd64
chmod +x act_runner-0.2.6-linux-amd64
mv act_runner-0.2.6-linux-amd64 /usr/local/bin/act_runner

# Register the runner with your Gitea instance
act_runner register --no-interactive \
  --instance https://your-gitea-domain.com \
  --token YOUR_REGISTRATION_TOKEN \
  --name production-runner \
  --labels ubuntu-latest:docker://node:20-bookworm

Get your registration token from Gitea's admin panel under Site Administration → Actions → Runners.

Start the runner as a service:

act_runner daemon

Your First Pipeline

Create .gitea/workflows/ci.yaml in your repository:

name: Build and Test

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup 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: Run linting
        run: npm run lint

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build application
        run: npm run build

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

Push to your repository, and Gitea will automatically trigger the workflow.

A Real Deployment Pipeline

Here's a production-ready pipeline that builds a Docker image and deploys it:

name: Deploy to Production

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.REGISTRY_URL }}
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ secrets.REGISTRY_URL }}/myapp:${{ github.ref_name }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            docker pull ${{ secrets.REGISTRY_URL }}/myapp:${{ github.ref_name }}
            docker compose up -d

Store your secrets in Gitea under Repository Settings → Actions → Secrets.

What Works (and What Doesn't)

Works great:

  • Most actions/* official actions (checkout, setup-node, cache, upload-artifact)
  • Docker actions and container-based workflows
  • Matrix builds and job dependencies
  • Secrets and environment variables
  • Workflow triggers (push, pull_request, schedule, manual)

Has quirks:

  • Some third-party actions need minor adjustments
  • GitHub-specific features (like GITHUB_TOKEN permissions) work differently
  • Marketplace browsing happens on GitHub, not Gitea

Doesn't work:

  • GitHub Packages integration (use your own registry)
  • GitHub-hosted runners (that's the point—you host your own)

The Cost Breakdown

Let's compare a 20-person team over one year:

Solution Annual Cost
GitHub Team $960 + compute overages
GitLab Premium $3,588
Gitea on Elestio ~$348 (infrastructure only)

With Gitea, you're paying for infrastructure, not seats. Add more developers without adding cost.

Scaling Your Runners

For larger teams, run multiple runners with different labels:

# High-CPU runner for builds
act_runner register --labels build-runner:docker://ubuntu:22.04

# GPU runner for ML workflows
act_runner register --labels gpu-runner:docker://nvidia/cuda:12.0-base

Then target specific runners in your workflows:

jobs:
  ml-training:
    runs-on: gpu-runner

Getting Started

The fastest way to get running is a managed Gitea deployment on Elestio. You'll have Actions-ready Gitea in minutes, then just spin up runners on your preferred infrastructure.

If you're migrating from GitHub, start by copying your workflow files to .gitea/workflows/. Most will work immediately. Fix edge cases as you find them.

Troubleshooting

Runner not picking up jobs: Check that runner labels match your workflow's runs-on value. Run act_runner list to see registered labels.

Action not found: Some actions reference GitHub-specific paths. Try pinning to a specific version or finding a Gitea-compatible fork.

Docker permission errors: Ensure the runner user has access to the Docker socket. Add them to the docker group.

Slow builds: Enable caching in your workflows. The actions/cache action works with Gitea's built-in cache server.

Thanks for reading.