Streamlining CI/CD: How to Configure Gitea Actions on a Private VPS for Automated Docker Builds and GHCR Deployment
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:packagesandread:packagesscopes 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 = trueSave 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: alwaysNote: 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 -dVerify 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.
- Navigate to your Gitea repository, then go to Settings > Actions > Secrets.
- Click Add Secret. Create a secret named
GH_USERNAMEand input your GitHub username as the value. - Click Add Secret again. Create a secret named
GHCR_PATand 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
mainbranch, 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.iousing the system secrets we mapped in Step 3. - docker/build-push-action@v4: Automates the build process using your local
Dockerfilecontext, 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
latesttag; 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 -fto clean out dangling build caches generated by theact_runnerover time.
