Gitea Actions CI/CD Configuration and Workflow Automation
Complete reference for configuring and using Gitea Actions as a built-in CI/CD solution. Covers .gitea directory structure, workflow YAML syntax, runner setup, GITEA_TOKEN permissions, secrets, variables, scoped workflows, and advanced patterns for automated builds, tests, and deployments.
Introduction
Gitea Actions is a built-in CI/CD solution integrated directly into Gitea, available since version 1.19 and enabled by default since 1.21. It provides a GitHub Actions-compatible workflow system that allows you to automate builds, tests, and deployments entirely on your own infrastructure. The system consists of two main components: the Gitea instance itself (which schedules and manages workflows) and one or more Gitea Runners (formerly called act_runner) that execute the actual jobs.
Gitea Actions is designed to be highly compatible with GitHub Actions -- most existing GitHub Actions workflows can be reused with minimal changes, and the workflow YAML syntax is nearly identical. This makes it straightforward for teams already familiar with GitHub Actions to adopt Gitea Actions for self-hosted automation. Key differences include support for absolute action URLs (pulling actions from any git repository), Go-based custom actions, and Gitea-specific context objects (gitea.*) that provide access to repository and workflow metadata.
When to use this guidance: When setting up automated CI/CD pipelines for repositories hosted on a self-managed Gitea instance, or when migrating from GitHub Actions to a self-hosted Gitea environment.
Core Concepts
The .gitea Directory
Workflow definitions are stored as YAML files in the .gitea/workflows/ directory at the root of your repository. For backwards compatibility with GitHub, .github/workflows/ is also supported. Each YAML file in this directory defines one or more workflow configurations.
your-repository/
.gitea/
workflows/
build.yaml # Build and test workflow
deploy.yaml # Deployment workflow
lint.yaml # Linting workflow
scoped_workflows/ # Gitea 1.28+ -- scoped workflows from source repos
Architecture Overview
Gitea Actions has a distributed architecture with four key network connections:
- Runner to Gitea instance: The runner polls the Gitea instance for queued jobs and reports execution results. This is the only connection the runner must establish.
- Job containers to Gitea instance: When actions like
actions/checkout@v4clone code, the temporary job containers connect to Gitea to fetch repository data. - Runner to internet: The runner downloads action scripts (from github.com by default, or from your Gitea instance if configured) and Docker images.
- Job containers to internet: Setup actions (e.g.,
actions/setup-go@v5) may download additional resources from the internet.
Internet access for connections 3 and 4 is optional -- you can configure your Gitea instance as both an actions marketplace (via mirroring GitHub repositories) and a container registry, creating a fully offline CI/CD pipeline.
Workflow Lifecycle
- An event occurs (push, pull request, schedule, manual dispatch, etc.)
- Gitea scans all
.gitea/workflows/*.yamlfiles in the repository - For each workflow whose
on:triggers match the event, a workflow run is created - The run is queued and assigned to an available runner whose labels match the job's
runs-on - The runner executes each job's steps sequentially within isolated containers
- Results and logs stream back to the Gitea instance in real time
Expressions and Contexts
Workflows use ${{ }} expression syntax to access contextual information. Both github. (for GitHub Actions compatibility) and gitea. (recommended for Gitea-native workflows) are supported.
Gitea-specific context properties:
gitea.actor-- the user who triggered the workflowgitea.event_name-- the name of the event that triggered the workflowgitea.ref-- the Git ref the workflow was triggered ongitea.repository-- theowner/repoidentifiergitea.workspace-- the workspace directory path on the runnergitea.workflow-- the workflow name
Additional contexts:
runner.os-- the operating system of the runnerjob.status-- the current status of the job (success, failure, cancelled)secrets.*-- secrets accessible to the workflowvars.*-- configuration variables (Gitea 1.26+)env.*-- environment variables set within the workflow
Directory Structure and File Organization
Workflow Directory
.gitea/workflows/
All workflow YAML files must be placed in this directory (or .github/workflows/). Each file can contain one or more workflow definitions. Gitea scans all files in this directory on every event and queues matching workflows.
Scoped Workflows Directory (Gitea 1.28+)
.gitea/scoped_workflows/
Scoped workflows are defined in a central source repository and automatically applied to consuming repositories at the organization or instance level. This feature is useful for enforcing consistent CI standards (linting, security scans, compliance checks) across many repositories without copying workflow files.
Configured via [actions] SCOPEDWORKFLOWDIRS = .gitea/scopedworkflows in app.ini. Can be multiple comma-separated directories. Must not overlap with WORKFLOWDIRS.
Directory Naming and Permissions
- The
.gitea/directory is a hidden directory (dotfile) tracked in Git - Files must be committed to the repository to take effect
- Workflow files must have
.yamlor.ymlextension - Each repository must have Actions enabled in its settings (Settings -> Repository -> Enable Repository Actions)
Workflow YAML Syntax
Minimal Workflow
name: CI Pipeline
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Build complete"
Complete Workflow Structure
name: Build, Test, and Deploy
run-name: ${{ gitea.actor }} triggered build on ${{ gitea.ref }}
# Trigger events
on:
push:
branches: [main, develop]
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
branches: [main]
workflow_dispatch: # Manual trigger
# Concurrency control
concurrency:
group: ${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true
# Environment variables available to all jobs
env:
NODE_VERSION: '18'
REGISTRY: ghcr.io
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: test
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/test
build:
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
needs: [build]
if: gitea.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying ${{ gitea.sha }}"
# deployment commands here
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
Matrix Builds
jobs:
test-matrix:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [16, 18, 20]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
Trigger Event Types
| Event | Activity Types | Description |
|---|---|---|
push | N/A | Triggered on git push to the repository |
pull_request | opened, edited, closed, reopened, assigned, unassigned, synchronized, labeled, unlabeled | PR lifecycle events. Note: ref is refs/pull/:prNumber/head (the PR head, not a merge preview) |
pullrequestreview | submitted, edited | Pull request review submission |
pullrequestreview_comment | created, edited | Comments on PR diffs |
issues | opened, edited, closed, reopened, assigned, unassigned, milestoned, demilestoned, labeled, unlabeled | Issue lifecycle events |
issue_comment | created, edited, deleted | Comments on issues and PRs |
create | N/A | Branch or tag creation |
delete | N/A | Branch or tag deletion |
release | published, edited | Release publishing |
workflow_dispatch | N/A | Manual trigger via the Gitea UI or API |
workflow_run | requested, completed | Triggered by another workflow's completion |
schedule | N/A | Cron-based scheduling. Supports standard cron syntax and non-standard @yearly, @monthly, @weekly, @daily, @hourly |
fork | N/A | Repository fork |
gollum | N/A | Wiki page creation or update |
registry_package | published | Container registry package publication |
Schedule (Cron) Examples
on:
schedule:
# Every day at midnight UTC
- cron: '0 0 * * *'
# Every Monday at 9 AM
- cron: '0 9 * * 1'
# Non-standard Gitea extensions (not supported by GitHub)
- cron: '@daily'
- cron: '@weekly'
Runner Setup and Configuration
Runner Architecture
Gitea Runners are standalone programs written in Go that poll the Gitea instance for queued jobs, execute them, and report results. Based on a fork of nektos/act, they support three execution modes:
- Host mode: jobs run directly on the host machine (no container isolation)
- Docker mode (recommended): jobs run in isolated Docker containers
- Docker-in-Docker (DinD): the runner itself runs in a container with its own Docker daemon (best security isolation)
Registration Token
Before a runner can execute jobs, it must be registered with the Gitea instance using a registration token. Tokens can be obtained at three levels:
- Instance level: Admin settings page (
<your_gitea.com>/-/admin/actions/runners) - Organization level: Organization settings page (
<your_gitea.com>/<org>/settings/actions/runners) - Repository level: Repository settings page (
<your_gitea.com>/<owner>/<repo>/settings/actions/runners)
Registration tokens are valid for registering multiple runners until explicitly revoked via the web UI or API.
Runner Installation via Binary
# Download the runner binary
chmod +x runner
# Generate the default config file
./runner generate-config > config.yaml
# Register the runner (non-interactive)
./runner register --no-interactive \
--instance https://gitea.example.com \
--token YOUR_REGISTRATION_TOKEN \
--name my-runner \
--labels "ubuntu-latest:docker://node:20-bullseye,custom-label:docker://centos:8"
# Start the runner daemon
./runner daemon --config config.yaml
Runner Installation via Docker
docker run \
-e GITEA_INSTANCE_URL=https://gitea.example.com \
-e GITEA_RUNNER_REGISTRATION_TOKEN=YOUR_TOKEN \
-e GITEA_RUNNER_NAME=gitea-runner-1 \
-e GITEA_RUNNER_LABELS="ubuntu-latest:docker://node:20-bullseye" \
-v /var/run/docker.sock:/var/run/docker.sock \
--name gitea-runner \
-d docker.io/gitea/runner:latest
Runner Installation via Docker Compose
version: "3.8"
services:
runner:
image: docker.io/gitea/runner:latest
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "${INSTANCE_URL}"
GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "${RUNNER_NAME}"
GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
volumes:
- ./config.yaml:/config.yaml
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
Runner as a Systemd Service
[Unit]
Description=Gitea Actions runner
Documentation=https://gitea.com/gitea/runner
After=docker.service
[Service]
ExecStart=/usr/local/bin/runner daemon --config /etc/runner/config.yaml
ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=/var/lib/runner
TimeoutSec=0
RestartSec=10
Restart=always
User=runner
[Install]
WantedBy=multi-user.target
Runner Labels
Labels determine which jobs a runner can accept and how they are executed. The format is:
label[:schema[:args]]
ubuntu-latest:docker://node:20-bullseye-- Run jobs labeledubuntu-latestusing a Docker container with thenode:20-bullseyeimagelinuxamd64:host-- Run jobs labeledlinuxamd64directly on the host machinecustom-label:docker://centos:8-- Run jobs labeledcustom-labelusing a CentOS 8 container
Default labels: ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster
Custom labels are specified during registration with --labels. Starting with Gitea 1.21, labels can be modified by editing runners.labels in the runner configuration file and restarting the daemon.
Ephemeral Runners
Ephemeral runners (Gitea Runner 0.2.12+) provide enhanced security for organization- or instance-level runners. An ephemeral runner executes exactly one job and then terminates. Its credentials are automatically revoked after assignment, preventing it from polling further jobs and limiting the blast radius of compromised runners.
./runner register --no-interactive --ephemeral \
--instance https://gitea.example.com \
--token TOKEN \
--name ephemeral-runner \
--labels ubuntu-latest:docker://node:20-bullseye
For Docker deployments, set GITEARUNNEREPHEMERAL=1 and omit the /data volume (credentials are single-use).
Cache Configuration
When using actions/cache in workflows, the runner cache must be explicitly configured because the runner container and job containers are on different networks by default.
# In the runner's config.yaml
cache:
enabled: true
dir: ""
host: "192.168.8.17" # LAN IP of the runner host
port: 8088
When starting the Docker container, map the cache port:
docker run --name gitea-runner \
-p 8088:8088 \
-d docker.io/gitea/runner:nightly
GITEA_TOKEN Permissions
Every Actions job receives an automatic token (${{ secrets.GITEA_TOKEN }}) that authenticates API requests and Git operations against the Gitea instance.
Permission Resolution Order
- Job-level permissions:
jobs.<job_id>.permissions - Workflow-level permissions (top-level
permissions:) - Default permissions from settings (owner or repository)
- Clamped by configured maximum token permissions
Permission Syntax
# Read-only for all scopes
permissions: read-all
# Write for all scopes
permissions: write-all
# Granular scoped permissions
permissions:
contents: read
issues: write
pull-requests: none
actions: read
wiki: none
projects: none
packages: read
Supported Scopes
| Scope | Controls Access To |
|---|---|
contents | Code and releases (combined) |
code | Repository source code |
releases | Repository releases |
issues | Issue tracking |
pull-requests | Pull requests |
actions | Actions management |
wiki | Repository wiki |
projects | Project boards |
packages | Package registry |
Valid access modes: read, write, none.
Default Permission Mode
- Permissive (default): Read and write permissions for most units in the job's repository
- Restricted: Read-only for code, releases, and packages; no access to other units
Configured in Settings -> Actions -> General at user, organization, or repository level.
Fork Pull Request Security
Workflows triggered by pull requests from forked repositories always receive read-only permissions for repository contents, regardless of workflow permissions: settings or configuration. This prevents malicious code in forks from modifying repository resources.
Cross-Repository Access
By default, GITEA_TOKEN only has access to the job's repository (computed permissions) and public repositories (read-only). Additional private repository access can be configured in Settings -> Actions -> General -> Cross-Repository Access.
Secrets Management
Secrets store sensitive information (API keys, passwords, tokens) that workflows need but must not be exposed in logs or repository files.
steps:
- name: Deploy to production
run: deploy.sh ${{ secrets.DEPLOY_KEY }}
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
Secret Naming Rules
- Only alphanumeric characters (
[a-z],[A-Z],[0-9]) and underscores (_) - Must not start with
GITHUBorGITEAprefix - Must not start with a number
- Case-insensitive (uppercased by convention)
- Must be unique at the creation level
Secret Scoping and Precedence
Secrets can be defined at user, organization, or repository level. When the same secret name exists at multiple levels, the lowest level takes precedence:
Repository-level secret (highest priority)
> Organization-level secret
> User-level secret (lowest priority)
Workflow Best Practices
1. Use Gitea-Specific Context Properties
Rationale: Using gitea. instead of github. makes your workflows Gitea-native and avoids confusion in mixed environments. Both work identically, but gitea.* ensures forward compatibility with Gitea-specific features that have no GitHub equivalent.
- name: Print build info
run: |
echo "Triggered by: ${{ gitea.actor }}"
echo "Event: ${{ gitea.event_name }}"
echo "Branch: ${{ gitea.ref }}"
echo "Repository: ${{ gitea.repository }}"
2. Always Pin Action Versions
Rationale: Pinning action versions (e.g., @v4 instead of @main or @latest) prevents unexpected breaking changes from upstream updates.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/cache@v4
3. Use Job Dependencies and Conditional Execution
Rationale: Structuring workflows with needs: creates clear pipelines, and if: conditions prevent unnecessary execution on non-matching branches or events.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
test:
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
deploy:
needs: [test]
if: gitea.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
4. Use Matrix Strategy for Multi-Environment Testing
Rationale: Matrix builds test across multiple runtime versions and operating systems in parallel, catching compatibility issues early without duplicating job definitions.
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements.txt
- run: pytest
5. Secure Secrets with Minimal Scope
Rationale: Using granular permissions: reduces the blast radius if a workflow is compromised. Never use permissions: write-all when only specific scopes are needed.
jobs:
lint:
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- run: npm run lint
6. Use Concurrency Control to Prevent Duplicate Runs
Rationale: Concurrency groups cancel or queue redundant runs, saving runner resources and preventing race conditions during deployments.
concurrency:
group: ${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true
7. Use Service Containers for Integration Tests
Rationale: Service containers provide ephemeral dependencies (databases, message queues, caches) that start and stop with the job, keeping tests isolated and repeatable.
jobs:
integration-test:
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: testdb
POSTGRES_PASSWORD: secret
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- run: npm run test:integration
8. Cache Dependencies for Speed
Rationale: Caching npm, pip, Maven, or Go module caches dramatically reduces workflow execution time.
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-
Scoped Workflows (Gitea 1.28+)
Scoped workflows allow a central source repository to provide workflow files that automatically run on consuming repositories, without copying files into each repository.
Use Cases
- Mandatory linting and formatting checks across an organization
- Security scanning and dependency auditing for all repositories
- Compliance and policy enforcement at scale
- Standardized build and release pipelines
Levels
- Owner level: Registered by an organization or user; applies to all repositories owned by that entity
- Instance level: Registered by a site administrator; applies to every repository on the Gitea instance
Configuration
[actions]
SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows
Source Repository Workflow
# .gitea/scoped_workflows/lint.yaml in the source repository
name: Organization-wide Linting
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install ruff
- run: ruff check .
The source repository is registered via Settings -> Actions -> Scoped Workflows at the organization, user, or instance level.
How Scoped Workflows Execute
- The run belongs to and is visible in the consuming repository (its runners, secrets, GITEA_TOKEN, and branch are used)
- Workflow content is read from the source repository's default branch at event time
- The consuming repository's event determines which
on:triggers match - Runs can be rerun, viewed in logs, and post commit statuses like any normal workflow
Required Workflows and Merge Gating
A scoped workflow can be marked as required. When it is required:
- Consuming repositories cannot disable it
- Pull requests cannot be merged until a commit status from the required workflow passes
- The status check context format:
<source-repo>: <workflow-name> / <job> (<event>)
Common Pitfalls and Anti-Patterns
Pitfall: Registering the Runner with a Loopback Address
Problem: Using 127.0.0.1 or localhost as the instance URL during runner registration. Job containers spun up by the runner are in a different network namespace and cannot reach the Gitea instance at localhost.
Recommended approach: Always use the LAN IP address or hostname of the Gitea instance.
# Correct
./runner register --instance http://192.168.1.100:3000
# Wrong -- job containers cannot reach this
./runner register --instance http://127.0.0.1:3000
Pitfall: Hardcoding Secrets in Workflow Files
Problem: Embedding API keys, tokens, or passwords directly in the YAML file violates security best practices and exposes secrets to anyone with repository read access.
Recommended approach: Always use ${{ secrets.SECRET_NAME }} and manage secrets through the Gitea UI.
# Wrong
- run: deploy --token abc123def456
# Correct
- run: deploy --token ${{ secrets.DEPLOY_TOKEN }}
Pitfall: Using Unversioned Actions References
Problem: Using @main or @latest for action versions means workflow behavior can change unexpectedly when the action is updated.
Recommended approach: Pin to a specific major version tag or commit SHA.
# Wrong
- uses: actions/checkout@main
# Correct
- uses: actions/checkout@v4
Pitfall: Ignoring the Gitea Context for Expressions
Problem: Using github.* context exclusively when working exclusively with Gitea. While compatible, this misses out on Gitea-specific properties and makes migration documentation harder to write.
Recommended approach: Use gitea.* for Gitea-native workflows.
# Works, but not Gitea-idiomatic
- run: echo ${{ github.repository }}
# Gitea-native approach
- run: echo ${{ gitea.repository }}
Pitfall: Not Configuring Cache for Actions Cache
Problem: Using actions/cache@v4 without configuring the runner cache host/port results in timeout errors because the job container cannot reach the runner container's default cache server.
Recommended approach: Configure cache.host and cache.port in the runner config.yaml and map the port in Docker.
Pitfall: Running Jobs on the Host Without Isolation
Problem: Host-mode runners execute job scripts directly on the runner machine without container isolation, posing security and cleanliness risks (stale files, conflicting dependencies, broader attack surface).
Recommended approach: Use Docker mode (the default) whenever possible. Reserve host mode for special cases like low-resource environments or OS-level operations.
Performance, Security, and Scalability Considerations
Security
- Runner trust model: Never use a runner you don't trust, and never provide a runner to a repository you don't trust. These two rules are the foundation of Gitea Actions security.
- Docker socket exposure: Mounting
/var/run/docker.sockinto the runner container gives jobs full Docker access. A malicious workflow could compromise the host through this socket. Use DinD mode for better isolation in multi-tenant environments. - Ephemeral runners: For public or multi-tenant instances, use ephemeral runners that self-destruct after one job. This prevents credential reuse and limits blast radius.
- Fork PR restrictions: Workflows from forked repositories automatically get read-only permissions for contents, preventing unauthorized modifications.
- Secret scrubbing: Gitea automatically masks secret values in workflow logs.
Performance
- Runner placement: Deploy runners on the same network as the Gitea instance for low-latency job communication. Use separate machines for runners to avoid resource contention.
- Caching: Configure
actions/cacheproperly to reduce dependency installation time. Cache keys should be specific enough to be useful but broad enough for cache hits. - Concurrency control: Use
concurrencygroups to cancel stale runs and prevent resource waste. - Matrix parallelism: Matrix jobs run in parallel, so design matrix strategies to maximize parallelism while respecting runner capacity.
Scalability
- Multiple runners: Register multiple runners (on different machines or as Docker containers) to handle concurrent job queues. Runners at different levels (instance, organization, repository) provide flexible load distribution.
- Custom runner images: Pre-build Docker images with frequently used tools and dependencies to reduce per-job setup time.
- Offline operation: For air-gapped environments, mirror actions repositories to your Gitea instance (set
DEFAULTACTIONSURL = self) and use your Gitea container registry for Docker images.
Differences from GitHub Actions
Gitea-Specific Features (Not Available in GitHub)
- Absolute action URLs: Actions can be referenced by full URL:
uses: https://gitea.example.com/owner/repo@branch - Go-based actions: Gitea supports writing custom actions in Go
- Extended schedule syntax:
@yearly,@monthly,@weekly,@daily,@hourlyin addition to standard cron - Scoped workflows: Centralized workflow enforcement across multiple repositories
Unsupported GitHub Actions Features
| Feature | Status | Workaround |
|---|---|---|
timeout-minutes | Ignored | Monitor job duration externally |
continue-on-error | Ignored | Use if: ${{ !cancelled() }} patterns |
environment | Ignored | Manage environments via secrets and conditions |
Complex runs-on groups | Partial support | Use single labels or arrays of labels |
| Problem Matchers | Not supported | Parse output manually in steps |
| Error annotations | Not supported | Use structured step output |
id-token scope | Not supported | Use OIDC-compatible approaches |
| Package repo auth with GITEA_TOKEN | Not implemented | Use a Personal Access Token (PAT) |
Behavior Differences
- Downloading actions:
DEFAULTACTIONSURLdefaults togithub. Set toselffor intranet environments where actions are mirrored locally. - Context availability: Gitea does not enforce context availability restrictions, so the
envcontext can be used in more places than GitHub Actions allows. - PR ref: Gitea uses
refs/pull/:prNumber/head(PR head) instead of GitHub'srefs/pull/:prNumber/merge(merge preview). Usegitea.eventname == 'pullrequest'to handle PR-specific logic.
Edge Cases and Advanced Usage
Network Architecture Troubleshooting
Gitea Actions has four network connections:
Act Runner ──(1)──> Gitea Instance
Job Containers ──(2)──> Gitea Instance
Act Runner ──(3)──> Internet (for action scripts, optional)
Job Containers ──(4)──> Internet (for setup tools, optional)
If jobs are queued but never start, verify:
- The runner is running and registered (
./runner daemonlogs) - The runner can reach the Gitea instance (connection 1)
- Labels match: the runner's registered labels include the job's
runs-on
If actions/checkout fails, verify:
- Job containers can reach the Gitea instance (connection 2)
- The runner was registered with a non-loopback address
Using Custom Actions from Private Repositories
To use actions from private repositories in workflows:
- In the source action repository: Settings -> Actions -> General -> add collaborative owners
- Additional private repositories can also be authorized via Settings -> Actions -> General -> Cross-Repository Access
Full Offline/Intranet Setup
For air-gapped Gitea instances:
- Set
[actions].DEFAULTACTIONSURL = selfinapp.ini - Mirror required actions repositories from GitHub to your Gitea instance
- Use absolute URLs for actions from other sources:
uses: https://gitea.internal/team/action@v1 - Pre-pull Docker images to your Gitea Container Registry
- Configure the runner without internet access (use internal DNS and image registries)
Related Documentation
- Git Best Practices
- Docker Best Practices
- Gitea official documentation: https://docs.gitea.com/usage/actions/
- Gitea Runner repository: https://gitea.com/gitea/runner
Glossary
- Workflow: An automated process defined in a YAML file consisting of one or more jobs
- Job: A set of steps that execute on the same runner
- Step: An individual task (a shell command or an action)
- Action: A reusable custom script/plugin referenced via
uses: - Runner: A program that executes workflow jobs, polled by Gitea for work
- GITEA_TOKEN: An automatically generated token for authenticating API and Git requests from within a workflow job
- Label: A tag on a runner that determines which jobs it accepts (e.g.,
ubuntu-latest) - Event: A repository activity that triggers a workflow (push, pull_request, etc.)
- Scoped Workflow: A workflow defined in a central repository that runs automatically on consuming repositories
- Ephemeral Runner: A runner that executes exactly one job and terminates, for enhanced security
Maintenance note: When updating this document, also update last_updated and verify compatibility with the latest Gitea version. Gitea Actions is under active development; check docs.gitea.com for new features in each release.