Gitea Actions brings GitHub Actions-style CI/CD to a self-hosted Gitea server (Forgejo has the equivalent Forgejo Actions). Workflows use the same YAML syntax, and most actions from the GitHub marketplace work unchanged. Jobs run on act_runner, a lightweight runner you host yourself. Here’s how to set it up and write your first pipeline.

If you haven’t installed Gitea yet, see Gitea self-hosted Git.

1. Enable Actions in Gitea

Recent Gitea versions enable Actions by default. If not, in app.ini:

[actions]
ENABLED = true

Restart Gitea. Each repository then has an Actions tab (enable it in the repository settings if needed).

2. Run an act_runner

Runners execute jobs, usually inside Docker containers. Run them on a separate VPS from Gitea if builds are heavy. Builds consume CPU, RAM and disk.

Get a registration token in Gitea: Site Administration → Actions → Runners → Create new runner (or at organisation or repository level).

With Docker Compose:

# /srv/act-runner/compose.yaml
services:
  runner:
    image: gitea/act_runner:latest
    restart: unless-stopped
    environment:
      GITEA_INSTANCE_URL: https://git.example.com
      GITEA_RUNNER_REGISTRATION_TOKEN: <token>
      GITEA_RUNNER_NAME: runner-1
      GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
    volumes:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock
docker compose up -d
docker compose logs -f

The runner appears in Gitea as idle.

Labels map the runs-on: names in workflows to container images. catthehacker/ubuntu:act-* images include many tools that GitHub’s hosted runners have. Lighter images such as node:20-bookworm start faster.

3. Your first workflow

Create .gitea/workflows/ci.yaml in a repository (.github/workflows/ also works):

name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm test

Push, then watch the run in the repository’s Actions tab.

Actions referenced as actions/checkout@v4 are fetched from GitHub by default (DEFAULT_ACTIONS_URL in Gitea’s config). You can mirror them on your Gitea for fully offline setups.

4. Secrets and variables

Add secrets in Repository → Settings → Actions → Secrets (or at organisation level), and use them as ${{ secrets.NAME }}:

      - name: Deploy over SSH
        env:
          SSH_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          mkdir -p ~/.ssh && echo "$SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H app.example.com >> ~/.ssh/known_hosts
          rsync -az --delete ./dist/ [email protected]:/var/www/app/
          ssh [email protected] 'sudo systemctl reload nginx'

Use a dedicated deploy key with minimal rights. See GitHub Actions deploy to VPS (same patterns) and secrets management.

5. Build and push Docker images

Gitea includes a container registry (git.example.com/OWNER/IMAGE):

      - uses: docker/login-action@v3
        with:
          registry: git.example.com
          username: ${{ gitea.actor }}
          password: ${{ secrets.REGISTRY_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: git.example.com/myorg/myapp:${{ gitea.sha }}

Building images inside job containers needs Docker access. The socket mount above provides it. See the security notes.

Differences from GitHub Actions

  • Context names: gitea.* alongside github.* (most github.* references work for compatibility).
  • Some features differ or are missing (certain permissions, OIDC tokens, larger runner features). Check Gitea’s compatibility notes when porting complex workflows.
  • Caching: actions/cache works with the runner’s built-in cache server (configured in the runner’s config).

Runner security

  • A runner with the Docker socket mounted can control the host’s Docker, which effectively means root on the runner machine. Run runners on dedicated VPSes, not on the Gitea server or production hosts. See Docker security.
  • Don’t run untrusted pull requests (forks) on runners holding production secrets. Restrict which repositories or organisations a runner serves.
  • Clean up regularly: docker system prune in a cron job keeps runner disks from filling. See docker prune.
  • Keep act_runner and Gitea updated.

Quick reference

Task Where / command
Enable Actions [actions] ENABLED = true
Runner token Site Admin / Org / Repo → Actions → Runners
Workflow location .gitea/workflows/*.yaml
Secrets Settings → Actions → Secrets
Runner logs docker compose logs -f

Related: CI/CD explained and self-hosted Git options.

  • Git
  • CI/CD
  • Self-Hosting