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.*alongsidegithub.*(mostgithub.*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/cacheworks 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 prunein 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.