Back to articles
Technology Insight

How to Configure Gitea Actions on a Self-Hosted VPS to Automatically Build and Push Docker Images to GitHub Container Registry (GHCR)

May 30, 2026

Introduction to Modern CI/CD with Gitea Actions

In the modern software development lifecycle, continuous integration and continuous deployment (CI/CD) have evolved from luxury automation to business-critical necessities. While platforms like GitHub Actions and GitLab CI dominate the enterprise landscape, they often come with usage limits, data privacy concerns, and unpredictable scaling costs. For small-to-medium enterprises (SMEs) and independent engineering teams seeking maximum control over their infrastructure, Gitea has emerged as the premier self-hosted alternative.

With the introduction of Gitea Actions, an built-in CI/CD system compatible with GitHub Actions workflow syntax, organizations can now run sophisticated automation pipelines directly on their private Virtual Private Servers (VPS). This guide provides a comprehensive, production-grade walkthrough on configuring Gitea Actions on a self-hosted VPS. We will specifically focus on creating an automated pipeline that builds a Docker image upon a code commit and securely pushes it to the GitHub Container Registry (GHCR), combining the cost-efficiency of private hosting with the reliability of GitHub’s cloud registry.

---

Prerequisites and Infrastructure Requirements

Before proceeding with the implementation, ensure your environment meets the following baseline technical requirements to guarantee stability and optimal performance during the Docker compilation process:

  • A Dedicated VPS: Running a modern Linux distribution (Ubuntu 22.04 LTS or Debian 12 recommended) with at least 2 vCPUs and 4GB of RAM. Docker compilation can be CPU and I/O intensive.
  • Gitea Instance: Version 1.19 or higher installed and running on your VPS, accessible via HTTPS.
  • Docker Engine: Installed on the VPS where the Gitea Actions runner will execute workloads.
  • A GitHub Account: With a Personal Access Token (PAT) generated with write:packages and read:packages scopes to authenticate against GHCR.
---

Step 1: Enabling Gitea Actions on Your Instance

By default, Gitea Actions is disabled to conserve system resources. To activate it, you must modify your Gitea configuration file (usually located at /etc/gitea/app.ini or within your Docker volume mount).

Open the configuration file and append or modify the following configuration block:

[actions]
ENABLED = true

Once saved, restart your Gitea service using systemctl restart gitea or by restarting your Gitea Docker container. Upon reloading your Gitea web interface, you will notice an "Actions" tab available in your site administration panel and repository settings.

---

Step 2: Installing and Registering the Gitea Actions Runner (act_runner)

Gitea utilizes a daemon called act_runner to execute workflow jobs. This runner connects back to your Gitea instance via a secure polling mechanism. For maximum security, we recommend running the runner as a separate system service or an independent Docker container.

1. Obtain the Registration Token

Navigate to your Gitea instance. Go to Site Administration > Actions > Runners and click on Create New Runner. Copy the registration token displayed on the screen. This token is sensitive and authenticates your runner to your server.

2. Deploying the Runner via Docker Compose

Create a dedicated directory on your VPS for the runner and establish a docker-compose.yml file to manage its lifecycle:

version: "3.8"
services:
  runner:
    image: gitea/act_runner:latest
    environment:
      - GITEA_INSTANCE_URL=[https://your-gitea-domain.com](https://your-gitea-domain.com)
      - GITEA_REGISTRATION_TOKEN=YOUR_COPIED_REGISTRATION_TOKEN
      - RUNNER_NAME=vps-enterprise-runner
      - RUNNER_LABELS=ubuntu-latest:docker://node:18-bullseye,ubuntu-22.04:docker://ubuntu:22.04
    volumes:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always

Note: Mapping /var/run/docker.sock is essential because it allows the runner to spawn Docker containers side-by-side on the host VPS, enabling Docker-in-Docker functionality for building images.

Execute docker compose up -d to start the runner. Verify successful registration in the Gitea admin panel; the runner status indicator should switch to active (green).

---

Step 3: Configuring Secure Secrets in Gitea

Hardcoding credentials into deployment scripts is a severe security risk. Gitea Actions supports encrypted secrets at both the organization and repository levels, mirroring GitHub's UX paradigm.

Navigate to your specific repository settings in Gitea, go to Settings > Actions > Secrets, and add the following two variables:

  1. GHCR_USERNAME: Your GitHub username or organization name under which the image will be stored.
  2. GHCR_TOKEN: The GitHub Personal Access Token (PAT) generated during the prerequisite phase.
---

Step 4: Writing the CI/CD Workflow Pipeline

Gitea Actions interprets YAML files located in the .gitea/workflows/ or .github/workflows/ directory of your repository. Create a file named .gitea/workflows/build-push-ghcr.yml and insert the following enterprise-ready workflow structure:

name: Build and Push Docker Image to GHCR

on:
  push:
    branches:
      - main
    tags:
      - 'v*'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v3

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

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ secrets.GHCR_USERNAME }}
          password: ${{ secrets.GHCR_TOKEN }}

      - name: Extract Docker Metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ghcr.io/${{ secrets.GHCR_USERNAME }}/my-app
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            latest

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v4
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Deep Dive into Workflow Steps:

  • Triggering Mechanisms: The pipeline triggers on any push to the main branch or whenever a version semantic tag (e.g., v1.0.0) is created.
  • Docker Buildx Setup: Enables advanced build capabilities, including multi-architecture builds and efficient caching.
  • Metadata Extraction: Dynamically tags your images. If pushing to main, it tags the image as latest. If pushing a tag, it matches the version accurately.
  • Caching: The cache-from and cache-to parameters utilize Gitea’s internal artifact storage, dramatically reducing compilation time for subsequent builds.
---

Step 5: Execution, Validation, and Troubleshooting

Commit the workflow file and your application’s Dockerfile to your Gitea repository, then push the changes to the main branch. Navigate to the Actions tab within Gitea to view live execution logs.

If the workflow fails, consider checking these common failure points:

  • Docker Socket Permissions: Ensure the user running act_runner has explicit write access to /var/run/docker.sock.
  • Network Constraints: Ensure your VPS firewall allows outbound HTTPS traffic to ghcr.io and inbound traffic from your internal networks.
  • Token Scopes: Double-check that your GitHub PAT has not expired and holds explicit permissions to manage packages.

Once successful, log into your GitHub account, navigate to your Profile, and verify the new artifact under the Packages section. Your image is now ready for deployment anywhere worldwide.

---

Conclusion

By establishing Gitea Actions on an isolated VPS, your business achieves a major operational milestone: decoupling continuous integration capabilities from cloud provider premiums. This infrastructure layout guarantees data sovereignty within your own servers while outsourcing artifact distribution securely to GitHub Container Registry. As your engineering demands expand, scaling your pipeline is as simple as horizontally deploying more act_runner nodes across additional low-cost VPS instances.