--- title: "Gitea Actions CI/CD Configuration and Workflow Automation" description: "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." language: null framework: null category: devops_ci_cd tags: - gitea - ci-cd - actions - devops - automation - workflows - runners - continuous-integration - continuous-deployment - yaml - pipelines - self-hosted keywords: - Gitea Actions configuration - .gitea workflow directory - Gitea runner setup - GITEA_TOKEN permissions - scoped workflows - Gitea CI/CD pipeline - act_runner labels last_updated: 2026-07-08 difficulty: intermediate version: "Gitea 1.19+ (current 1.26.4, 1.28-dev)" related: - tools/git/best_practices.md - tools/docker/best_practices.md search_priority: high status: published --- # Gitea Actions CI/CD Configuration and Workflow Automation ## 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: 1. **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. 2. **Job containers to Gitea instance**: When actions like `actions/checkout@v4` clone code, the temporary job containers connect to Gitea to fetch repository data. 3. **Runner to internet**: The runner downloads action scripts (from github.com by default, or from your Gitea instance if configured) and Docker images. 4. **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 1. An event occurs (push, pull request, schedule, manual dispatch, etc.) 2. Gitea scans all `.gitea/workflows/*.yaml` files in the repository 3. For each workflow whose `on:` triggers match the event, a workflow run is created 4. The run is queued and assigned to an available runner whose labels match the job's `runs-on` 5. The runner executes each job's steps sequentially within isolated containers 6. 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 workflow - `gitea.event_name` -- the name of the event that triggered the workflow - `gitea.ref` -- the Git ref the workflow was triggered on - `gitea.repository` -- the `owner/repo` identifier - `gitea.workspace` -- the workspace directory path on the runner - `gitea.workflow` -- the workflow name **Additional contexts:** - `runner.os` -- the operating system of the runner - `job.status` -- the current status of the job (success, failure, cancelled) - `secrets.*` -- secrets accessible to the workflow - `vars.*` -- 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] SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows` in `app.ini`. Can be multiple comma-separated directories. Must not overlap with `WORKFLOW_DIRS`. ### 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 `.yaml` or `.yml` extension - Each repository must have Actions enabled in its settings (Settings -> Repository -> Enable Repository Actions) ## Workflow YAML Syntax ### Minimal Workflow ```yaml name: CI Pipeline on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: echo "Build complete" ``` ### Complete Workflow Structure ```yaml 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 ```yaml 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) | | `pull_request_review` | submitted, edited | Pull request review submission | | `pull_request_review_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 ```yaml 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: 1. **Host mode**: jobs run directly on the host machine (no container isolation) 2. **Docker mode (recommended)**: jobs run in isolated Docker containers 3. **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 (`/-/admin/actions/runners`) - **Organization level**: Organization settings page (`//settings/actions/runners`) - **Repository level**: Repository settings page (`///settings/actions/runners`) Registration tokens are valid for registering multiple runners until explicitly revoked via the web UI or API. ### Runner Installation via Binary ```bash # 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 ```bash 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 ```yaml 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 ```ini [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 labeled `ubuntu-latest` using a Docker container with the `node:20-bullseye` image - `linux_amd64:host` -- Run jobs labeled `linux_amd64` directly on the host machine - `custom-label:docker://centos:8` -- Run jobs labeled `custom-label` using 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. ```bash ./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 `GITEA_RUNNER_EPHEMERAL=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. ```yaml # 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: ```bash 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 1. Job-level permissions: `jobs..permissions` 2. Workflow-level permissions (top-level `permissions:`) 3. Default permissions from settings (owner or repository) 4. Clamped by configured maximum token permissions ### Permission Syntax ```yaml # 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. ```yaml 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 `GITHUB_` or `GITEA_` prefix - 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. ```yaml - 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. ```yaml 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. ```yaml 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. ```yaml 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. ```yaml 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. ```yaml 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. ```yaml 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. ```yaml - 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 ```ini [actions] SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows ``` ### Source Repository Workflow ```yaml # .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: `: / ()` ## 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. ```bash # 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. ```yaml # 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. ```yaml # 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. ```yaml # 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 1. **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. 2. **Docker socket exposure**: Mounting `/var/run/docker.sock` into 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. 3. **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. 4. **Fork PR restrictions**: Workflows from forked repositories automatically get read-only permissions for contents, preventing unauthorized modifications. 5. **Secret scrubbing**: Gitea automatically masks secret values in workflow logs. ### Performance 1. **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. 2. **Caching**: Configure `actions/cache` properly to reduce dependency installation time. Cache keys should be specific enough to be useful but broad enough for cache hits. 3. **Concurrency control**: Use `concurrency` groups to cancel stale runs and prevent resource waste. 4. **Matrix parallelism**: Matrix jobs run in parallel, so design matrix strategies to maximize parallelism while respecting runner capacity. ### Scalability 1. **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. 2. **Custom runner images**: Pre-build Docker images with frequently used tools and dependencies to reduce per-job setup time. 3. **Offline operation**: For air-gapped environments, mirror actions repositories to your Gitea instance (set `DEFAULT_ACTIONS_URL = 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`, `@hourly` in 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 1. **Downloading actions**: `DEFAULT_ACTIONS_URL` defaults to `github`. Set to `self` for intranet environments where actions are mirrored locally. 2. **Context availability**: Gitea does not enforce context availability restrictions, so the `env` context can be used in more places than GitHub Actions allows. 3. **PR ref**: Gitea uses `refs/pull/:prNumber/head` (PR head) instead of GitHub's `refs/pull/:prNumber/merge` (merge preview). Use `gitea.event_name == 'pull_request'` 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: 1. The runner is running and registered (`./runner daemon` logs) 2. The runner can reach the Gitea instance (connection 1) 3. Labels match: the runner's registered labels include the job's `runs-on` If `actions/checkout` fails, verify: 1. Job containers can reach the Gitea instance (connection 2) 2. The runner was registered with a non-loopback address ### Using Custom Actions from Private Repositories To use actions from private repositories in workflows: 1. In the source action repository: Settings -> Actions -> General -> add collaborative owners 2. Additional private repositories can also be authorized via Settings -> Actions -> General -> Cross-Repository Access ### Full Offline/Intranet Setup For air-gapped Gitea instances: 1. Set `[actions].DEFAULT_ACTIONS_URL = self` in `app.ini` 2. Mirror required actions repositories from GitHub to your Gitea instance 3. Use absolute URLs for actions from other sources: `uses: https://gitea.internal/team/action@v1` 4. Pre-pull Docker images to your Gitea Container Registry 5. Configure the runner without internet access (use internal DNS and image registries) ## Related Documentation - [Git Best Practices](../git/best_practices.md) - [Docker Best Practices](../docker/best_practices.md) - 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.