The goal of this guide is to turn your Gitea from a simple code repository into a full automation pipeline. We’ll use Gitea Actions, which is compatible with GitHub Actions syntax, making the process straightforward if you already know GitHub CI/CD.
1. The Concept: Gitea Actions and Runners
Unlike GitHub, where the machines that execute workflows (Runners) are provided by Microsoft, in a self-hosted environment you must run your own Runner.
Since your Gitea is already running in Docker, the most natural approach is to create a sibling container that acts as the Runner.
2. Enabling Actions in Gitea
First, you need to explicitly enable Actions in Gitea.
Edit the Gitea configuration file (usually located at gitea/conf/app.ini).
Add or update the following section:
ini
[actions]
ENABLED = true
Restart the Gitea container to apply the changes.
3. Creating the Gitea Runner (The Worker)
Now you need a service that continuously listens to Gitea and waits for jobs.
We’ll use the official act_runner.
Create a docker-compose.yml file for the runner (it can be in the same compose file as Gitea or a separate one):
services:
runner:
image: gitea/act_runner:latest
container_name: gitea-runner
environment:
- CONFIG_FILE=/config.yaml
- GITEA_INSTANCE_URL=http://192.168.x.x:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=<TOKEN_HERE>
- GITEA_RUNNER_NAME=thinkpad-runner
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
restart: always
Getting the Runner Registration Token
Go to Site Administration → Actions → Runners → Create new Runner.
Copy the generated token and paste it into the GITEA_RUNNER_REGISTRATION_TOKEN field.
4. The Workflow: From Push to Deployment
Inside your repository (for example, a Next.js or Python project), create a workflow that defines what happens when code is pushed.
Create the following file:
.gitea/workflows/deploy.yaml
Below is a simple end-to-end example.
Scenario:
You push new code to main, a Docker image is built, pushed to a registry, and the production container is updated automatically.
name: Deploy to Production
run-name: ${{ gitea.actor }} is deploying 🚀
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Login to Docker Hub (optional)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: your-user/your-app:latest
- name: Update production container
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.HOST_IP }}
username: ${{ secrets.HOST_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull your-user/your-app:latest
docker stop my-app-prod || true
docker rm my-app-prod || true
docker run -d \
--name my-app-prod \
-p 8080:80 \
your-user/your-app:latest
Why a Separate production Branch Is Usually a Bad Idea
You might consider creating a production branch and having Gitea automatically merge main into it.
In practice, this often introduces unnecessary complexity.
Modern CI/CD follows Trunk-Based Development principles:
main is the single source of truth
Code merged into main must always be production-ready
CI/CD builds and deploys directly from main
This eliminates manual merges, reduces errors, and keeps the pipeline simple.
Optional: Mirroring the Repository to GitHub
Even when using Gitea as your primary Git platform, mirroring your repository to GitHub can be a strategic advantage rather than a contradiction.
Why Mirror to GitHub?
- Visibility & Portfolio
GitHub is still the most widely used platform by recruiters and open-source communities. Mirroring ensures your work is publicly visible without changing your main workflow.
- Backup & Redundancy
Your self-hosted Gitea remains the source of truth, while GitHub acts as a read-only backup.
- Ecosystem Compatibility
Some tools, integrations, and third-party services still only support GitHub natively.
- Zero Workflow Impact
You continue pushing to Gitea, while GitHub stays automatically in sync.
How Gitea Repository Mirroring Works
Gitea supports one-way and two-way mirroring.
For most setups, one-way push mirroring is the safest option:
- Gitea → GitHub
- Main branch remains authoritative
- No accidental changes coming back from GitHub
Setting Up Push Mirroring (Gitea → GitHub)
- Create an empty repository on GitHub
- In Gitea, open your repository settings
- Navigate to Settings → Repository → Mirroring
- Add a new push mirror
- Use the GitHub repository URL (SSH is recommended)
- Add your GitHub SSH key or token (example: git@github.com:your-name/your-repo.git)
- Enable Push Mirror
- Save the configuration
From this point on, every push to Gitea is automatically mirrored to GitHub.
Recommended Best Practices
- Disable GitHub Actions on the mirrored repository Gitea remains responsible for CI/CD.
- Protect the GitHub repository Treat it as read-only to avoid divergence.
- Mirror only main (optional) Keeps the public repo clean and focused. Mental Model
Gitea = Source of Truth
GitHub = Public Mirror / Backup
CI/CD = Gitea Actions
This setup gives you full ownership and control, while still benefiting from GitHub’s network effects.
Summary: Key Benefits
Automation
- No more SSH access and manual git pull on the server.
Observability
- Each build and deployment is logged and visible directly in the Gitea UI.
Safety
- If the build fails (syntax errors, failing tests, broken TypeScript or Python code), the deployment is automatically blocked, keeping production stable.
Top comments (0)