Back to articles
Technology Insight

Streamlining CI/CD: How to Configure Gitea Actions for Docker Builds and GHCR Publishing on Your Private VPS

May 30, 2026

Introduction to Modern Self-Hosted CI/CD

In the evolving landscape of DevOps, balancing control, privacy, and performance often leads engineering teams to self-hosted solutions. Gitea has long been celebrated as a lightweight, highly efficient alternative to resource-heavy Git platforms. With the introduction of Gitea Actions—a built-in CI/CD engine compatible with GitHub Actions syntax—it has become a formidable tool for automating development workflows directly from a private Virtual Private Server (VPS).

For teams utilizing a hybrid infrastructure, a common architecture involves hosting source code locally on a private VPS while leveraging scalable cloud registries for deployment artifacts. This guide provides a comprehensive, step-by-step walkthrough on configuring Gitea Actions to automatically build a Docker image upon a code commit and push it securely to the GitHub Container Registry (GHCR).

Prerequisites and Architectural Overview

Before diving into the configuration, ensure your environment meets the following baseline requirements:

  • A private VPS running a modern Linux distribution (e.g., Ubuntu 22.04 LTS or later) with Gitea installed and accessible.
  • Administrative access to your Gitea instance to enable Actions and register runners.
  • Docker and Docker Compose installed on the VPS where the Gitea runner will execute.
  • A GitHub account with a Personal Access Token (PAT) configured with the necessary scopes to write to GHCR.
Architecture Note: The Gitea instance tracks your repository changes, the Gitea Runner (act_runner) executes the workflow steps on your VPS, and the final compiled artifact (the Docker image) is transmitted securely over HTTPS to GitHub's infrastructure.

Step 1: Enabling Gitea Actions and Setting Up the Runner

Gitea Actions is disabled by default in older versions or vanilla installations. To activate it, you must modify your Gitea configuration file, typically located at /etc/gitea/app.ini or within your Docker volume mappings.

Modifying app.ini

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

[actions]
ENABLED = true

Restart your Gitea service to apply the changes. Once restarted, you will notice an "Actions" tab appearing in your repository settings.

Deploying the Gitea Runner (act_runner)

The Gitea runner is a daemon that polls your Gitea instance for pending jobs. We will deploy it using Docker Compose for ease of management. Create a docker-compose.yml file for the runner:

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_RUNNER_REGISTRATION_TOKEN=YOUR_REGISTRATION_TOKEN
      - GITEA_RUNNER_NAME=vps-ci-runner
      - GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data
    restart: always

To obtain the YOUR_REGISTRATION_TOKEN, navigate to your Gitea Site Administration panel, select Actions > Runners, and click Create New Runner. Copy the token and paste it into your compose file, then execute docker compose up -d.

Step 2: Securing GitHub Container Registry (GHCR) Credentials

To allow your self-hosted VPS to push images to GHCR, you must authenticate against GitHub’s container service using a Personal Access Token (PAT). This prevents exposing your primary GitHub password.

Generating a GitHub PAT

  1. Log in to GitHub and navigate to Settings > Developer Settings > Personal Access Tokens > Tokens (classic).
  2. Click Generate new token (classic).
  3. Provide a descriptive note, such as "Gitea VPS Runner - GHCR".
  4. Select the following explicit scopes: write:packages (to upload images) and read:packages (to pull if necessary).
  5. Generate the token and copy the string immediately. It will not be displayed again.

Storing Secrets in Gitea

Never hardcode credentials into your workflow files. Instead, leverage Gitea’s secure encrypted secrets storage:

  • Navigate to your specific repository on Gitea.
  • Go to Settings > Actions > Secrets.
  • Click Add Secret.
  • Create a secret named GHCR_USERNAME with your GitHub username as the value.
  • Create a second secret named GHCR_TOKEN with your generated PAT as the value.

Step 3: Crafting the CI/CD Workflow Document

Gitea Actions utilizes the identical YAML syntax pioneered by GitHub Actions. Create a directory structure in the root of your project repository named .gitea/workflows/ and create a file named publish-ghcr.yml.

Insert the following comprehensive workflow configuration:

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 }}/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha,format=short

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

Step 4: Deep Dive into Workflow Mechanics

Understanding what occurs during execution is critical for long-term pipeline maintenance. Let us dissect the essential segments of the configuration script:

The Triggers

The on: directive dictates that this automation triggers exclusively when code is pushed to the main branch or when a release tag prefixed with v (e.g., v1.0.0) is created. This ensures development builds do not continuously clutter your production registry.

Authentication & Metadata Extraction

The docker/login-action connects to ghcr.io securely using the variables stored in your Gitea Secrets abstraction layer. Following successful authentication, the docker/metadata-action automatically formats the tags. For instance, a commit to main creates an image tagged :main, alongside a unique short Git commit SHA hash tag for granular tracking.

Optimized Caching Mechanisms

Notice the inclusion of cache-from: type=gha and cache-to: type=gha. Gitea Actions runners support caching layers locally. By utilizing these parameters, unchanged Docker layers are skipped in subsequent runs, reducing build times from several minutes down to mere seconds.

Step 5: Execution, Validation, and Troubleshooting

Commit your new workflow file along with a valid Dockerfile in your repository root, and push the changes to your Gitea VPS instance. Navigate to the Actions tab of your repository interface to monitor the real-time execution logs.

Common Pitfalls and Solutions

  • Error: "Cannot connect to the Docker daemon" – This indicates the Gitea Runner container cannot access the host's Docker socket. Verify that the volume mapping /var/run/docker.sock:/var/run/docker.sock is precisely specified in your Docker Compose file and that the user running the container has appropriate group permissions.
  • Error: "401 Unauthorized" on Push – Double-check your GitHub PAT scope allocations. Ensure write:packages is ticked, and check that your Gitea secret names exactly match the text inside the YAML workflow file.
  • Runner Stays Idle – Verify that the labels defined in your runner registration (e.g., ubuntu-latest) match the runs-on property in your workflow document. If they mismatch, the coordinator will never assign the job to that runner.

Conclusion

By integrating Gitea Actions with GitHub Container Registry, you establish a highly professional, resilient, and cost-effective CI/CD pipeline entirely hosted on your private VPS. This hybrid strategy preserves your autonomy over code custody while utilizing GitHub’s global edge network for distribution. Implement these steps to accelerate your container deployment loops and maintain maximum infrastructure transparency.

Streamlining CI/CD: How to Configure Gitea Actions for Docker Builds and GHCR Publishing on Your Private VPS | DPTCloud