Back to articles
Technology Insight

Streamlining CI/CD: How to Configure Gitea Actions on a Private VPS for Automated Docker Builds and GHCR Deployment

May 30, 2026

Introduction to Modern Self-Hosted CI/CD

In the evolving landscape of DevOps, balancing resource control, cost efficiency, and automation performance is a constant challenge. While cloud-native platforms like GitHub Actions or GitLab CI offer robust features, managing massive build pipelines can quickly become cost-prohibitive. Enter Gitea Actions—a built-in, lightweight CI/CD solution for Gitea that mirrors the syntax and design of GitHub Actions. By hosting your repository and CI/CD runner on a private Virtual Private Server (VPS), you achieve full control over your execution environment. In this comprehensive guide, we will walk through configuring Gitea Actions on a private VPS to build, package, and automatically push Docker images 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 Docker and Docker Compose installed.
  • A functional Gitea instance (version 1.21.0 or higher, as Gitea Actions must be natively supported).
  • A GitHub account with an active Personal Access Token (PAT) possessing write:packages and read:packages scopes to authenticate with GHCR.
  • Basic familiarity with YAML syntax, Dockerfiles, and SSH operations.
Why use Gitea with GHCR? This hybrid architecture allows you to maintain your primary codebase inside a lightweight, fast, self-hosted Git system on your VPS, while utilizing GitHub's globally distributed, highly available Container Registry to distribute your production-ready Docker images securely.

Step 1: Enabling Actions in Your Gitea Instance

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

Open your app.ini file and append or modify the following configuration block:

[actions]
ENABLED = true

Save the file and restart your Gitea service using systemctl restart gitea or docker restart gitea. Once restarted, you will notice a new "Actions" tab appearing in your repository settings.

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

Gitea relies on an external daemon called act_runner to execute workflow jobs. This runner runs safely isolated inside your VPS. The most seamless way to deploy the runner is via Docker Compose.

1. Obtain the Registration Token

Navigate to your Gitea instance. Go to Site Administration > Actions > Runners (for global access) or your specific Repository > Settings > Actions > Runners. Click on Create New Runner and copy the generated registration token.

2. Create the Docker Compose Configuration

On your VPS, create a dedicated directory for the runner and establish a docker-compose.yml file:

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_COPIED_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:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always

Note: Mounting /var/run/docker.sock is critical because it allows the runner to spawn Docker containers dynamically to execute your build jobs (Docker-in-Docker functionality).

3. Launch the Runner

Execute the following command to start your runner in detached mode:

docker compose up -d

Verify on your Gitea dashboard that the runner status switches to a green, active state.

Step 3: Configuring Gitea Secrets for GitHub Container Registry

To safely push images to GHCR without exposing credentials in your code repository, you must store your GitHub credentials as encrypted secrets within Gitea.

  1. Navigate to your Gitea repository, then go to Settings > Actions > Secrets.
  2. Click Add Secret. Create a secret named GH_USERNAME and input your GitHub username as the value.
  3. Click Add Secret again. Create a secret named GHCR_PAT and paste your pre-generated GitHub Personal Access Token here.

Step 4: Defining the CI/CD Workflow Pipeline

Gitea Actions interprets standard YAML workflow files stored within the .gitea/workflows/ directory of your project repository. Create a file named .gitea/workflows/deploy.yml and populate it with the following configuration:

name: Build and Push Docker Image to GHCR

on:
  push:
    branches:
      - main

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@v3

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

      - name: Log in to GitHub Container Registry
        run: echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u "${{ secrets.GH_USERNAME }}" --password-stdin

      - name: Extract Metadata for Docker
        id: meta
        run: |
          IMAGE_NAME=ghcr.io/${{ secrets.GH_USERNAME }}/my-app
          TAG=latest
          echo "image=${IMAGE_NAME}:${TAG}" >> $GITHUB_OUTPUT

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v4
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.image }}

Deconstructing the Workflow Logic

  • on.push.branches: Restricts the workflow execution so it triggers exclusively when code is pushed to the main branch, protecting production states.
  • actions/checkout@v3: Instructs the runner to clone your code from the VPS-hosted Gitea repository into the temporary job workspace.
  • docker/login: Authenticates securely against ghcr.io using the system secrets we mapped in Step 3.
  • docker/build-push-action@v4: Automates the build process using your local Dockerfile context, optimizes layering, and handles the upload network stream safely to GitHub's infrastructure.

Step 5: Testing and Monitoring the Automated Pipeline

To validate the setup, create a simple Dockerfile at the root of your project repository, make a commit, and push it to your main branch. For testing purposes, a basic Node.js or Nginx Dockerfile is sufficient:

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/

Once pushed, head over to the Actions tab of your repository in Gitea. You will see a live execution stream of your runner downloading the necessary build images, validating configurations, running the local Docker build context, and sequentially streaming layers up to the GitHub Container Registry.

Upon successful execution, log into your GitHub account, navigate to your profile's Packages tab, and verify that your newly compiled image is listed and available for deployment across your clusters.

Conclusion and Best Practices

By implementing Gitea Actions on your private VPS, you have constructed a lightweight, cost-effective, high-performance automated assembly line that maintains strict control over code privacy while delegating image distribution to a highly scalable platform like GHCR. As you advance this pipeline, consider adopting these additional best practices:

  • Immutability: Avoid relying solely on the latest tag; instead, dynamically inject short commit SHA hashes or semantic version tags (e.g., v1.0.1) into your image names during the metadata step.
  • Security Scanning: Integrate container vulnerability scanner tools like Trivy into your Gitea workflow steps prior to running the push execution.
  • Pruning: Set up a cron task on your VPS to periodically run docker system prune -f to clean out dangling build caches generated by the act_runner over time.
Streamlining CI/CD: How to Configure Gitea Actions on a Private VPS for Automated Docker Builds and GHCR Deployment | DPTCloud