Streamlining CI/CD: How to Configure Gitea Actions for Docker Builds and GHCR Publishing on Your Private VPS
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 = trueRestart 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: alwaysTo 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
- Log in to GitHub and navigate to Settings > Developer Settings > Personal Access Tokens > Tokens (classic).
- Click Generate new token (classic).
- Provide a descriptive note, such as "Gitea VPS Runner - GHCR".
- Select the following explicit scopes:
write:packages(to upload images) andread:packages(to pull if necessary). - 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_USERNAMEwith your GitHub username as the value. - Create a second secret named
GHCR_TOKENwith 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=maxStep 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.sockis 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:packagesis 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 theruns-onproperty 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.
