CI/CD on Gitea: Gitea Actions & Woodpecker CI
Two excellent options for CI/CD: Gitea Actions (GitHub Actions-compatible) or Woodpecker CI (container-first with advanced pipeline features).
Prerequisites: Complete Part 1 (Gitea Setup) before following this guide. You need a working Gitea instance accessible at your domain or IP.
Recommendation: Start with Gitea Actions if you are migrating from GitHub. Use Woodpecker if you need matrix builds, advanced caching, or per-step resource limits.
Gitea Actions vs Woodpecker CI
| Feature | Gitea Actions | Woodpecker CI |
|---|---|---|
| GitHub Actions syntax | Yes (compatible) | No (own YAML format) |
| Built into Gitea | Yes | No (separate service) |
| Docker-native runners | Via act_runner | Native |
| Matrix builds | Limited | Full support |
| Step-level caching | Basic | Advanced |
| Plugin ecosystem | GitHub Actions marketplace | Woodpecker plugins |
| Resource limits per step | No | Yes |
| Best for | GitHub migrants | Power users / larger teams |
Option A: Gitea Actions
Gitea Actions uses a separate act_runner process that polls your Gitea instance for queued jobs. Runners can be global (all repos), organization-level, or repository-level. The runner executes each workflow step inside Docker containers.
Enable Gitea Actions
Gitea Actions is disabled by default. Enable it in your app.ini configuration:
# Edit /opt/gitea/config/app.ini
# If using Docker, the file is mounted at ./config/app.ini
[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = https://gitea.com
# Restart Gitea to apply
cd /opt/gitea && docker compose restart giteaInstall act_runner
The act_runner is a standalone binary that connects to your Gitea instance and executes workflow jobs:
# Download the latest act_runner binary
wget https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
chmod +x act_runner-0.2.11-linux-amd64
sudo mv act_runner-0.2.11-linux-amd64 /usr/local/bin/act_runner
# Verify
act_runner --versionRegister the Runner
Generate a runner registration token in Gitea (Admin Panel → Actions → Runners → Create Runner Token), then register:
# Create a config directory
mkdir -p /opt/act_runner && cd /opt/act_runner
# Register the runner (interactive prompt)
act_runner register
# When prompted:
# Gitea instance URL: https://git.yourdomain.com
# Runner token: (paste token from Gitea UI)
# Runner name: ramnode-runner-01
# Runner labels: ubuntu-22.04:docker://node:20 (comma-separated)
# Start the runner
act_runner daemonRun act_runner as a Systemd Service
Create a systemd service for persistent operation:
[Unit]
Description=Gitea Actions Runner
After=network.target
[Service]
WorkingDirectory=/opt/act_runner
ExecStart=/usr/local/bin/act_runner daemon --config /opt/act_runner/.runner
Restart=always
User=root
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now act_runner
sudo systemctl status act_runnerYour First Gitea Actions Workflow
Create a .gitea/workflows/ci.yml file in any repository. The syntax is identical to GitHub Actions:
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build Docker image
run: |
docker build -t myapp:${{ gitea.sha }} .GitHub Actions Compatibility: Most actions from the GitHub Actions marketplace work in Gitea Actions when you set DEFAULT_ACTIONS_URL = https://gitea.com or point to a mirror. actions/checkout, actions/setup-node, and docker/* actions all work without modification.
Option B: Woodpecker CI
Woodpecker CI consists of two components: a server that manages pipelines and a UI, and one or more agents that actually execute pipeline steps. Agents are stateless and can be scaled horizontally.
Create a Gitea OAuth Application
Woodpecker authenticates with Gitea via OAuth:
- 1. In Gitea: User Settings → Applications → OAuth2 Applications
- 2. Application Name: Woodpecker CI
- 3. Redirect URI:
https://ci.yourdomain.com/authorize - 4. Save the Client ID and Client Secret for the next step
Deploy Woodpecker with Docker Compose
version: '3.8'
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:latest
container_name: woodpecker-server
restart: unless-stopped
ports:
- '8000:8000'
environment:
- WOODPECKER_OPEN=false
- WOODPECKER_HOST=https://ci.yourdomain.com
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=https://git.yourdomain.com
- WOODPECKER_GITEA_CLIENT=YOUR_CLIENT_ID
- WOODPECKER_GITEA_SECRET=YOUR_CLIENT_SECRET
- WOODPECKER_AGENT_SECRET=generate_with_openssl_rand_hex_32
- WOODPECKER_DATABASE_DRIVER=sqlite3
- WOODPECKER_DATABASE_DATASOURCE=/var/lib/woodpecker/woodpecker.db
volumes:
- woodpecker_data:/var/lib/woodpecker
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:latest
container_name: woodpecker-agent
restart: unless-stopped
depends_on: [woodpecker-server]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=generate_with_openssl_rand_hex_32
- WOODPECKER_MAX_WORKFLOWS=4
volumes:
woodpecker_data:cd /opt/woodpecker && docker compose up -d
docker compose logs -f woodpecker-serverSecurity note: Mounting /var/run/docker.sock gives the CI runner full access to your host Docker daemon. For shared or multi-tenant environments, consider using rootless Podman or Kaniko instead. For a single-developer RamNode VPS, the socket mount is acceptable.
Your First Woodpecker Pipeline
Create a .woodpecker.yml file in your repository root. Woodpecker's pipeline format is step-based and container-native:
pipeline:
install:
image: node:20-alpine
commands:
- npm ci
test:
image: node:20-alpine
commands:
- npm test
depends_on: [install]
build-image:
image: plugins/docker
settings:
repo: registry.yourdomain.com/myapp
tags:
- latest
- ${CI_COMMIT_SHA:0:8}
username:
from_secret: docker_username
password:
from_secret: docker_password
depends_on: [test]
when:
branch: mainShared Configuration
Docker-in-Docker: Building Container Images
Both Gitea Actions and Woodpecker can build Docker images during CI. Build and push to Gitea's built-in container registry:
# In your CI workflow (Gitea Actions syntax)
- name: Build and push Docker image
run: |
echo ${{ secrets.GITEA_TOKEN }} | docker login git.yourdomain.com -u ${{ gitea.actor }} --password-stdin
docker build -t git.yourdomain.com/your-org/myapp:${{ gitea.sha }} .
docker push git.yourdomain.com/your-org/myapp:${{ gitea.sha }}
docker tag git.yourdomain.com/your-org/myapp:${{ gitea.sha }} git.yourdomain.com/your-org/myapp:latest
docker push git.yourdomain.com/your-org/myapp:latestManaging Secrets
Repository Secrets in Gitea Actions
Add secrets in the repository UI under Settings → Secrets and Variables → Actions. Reference them as ${{ secrets.MY_SECRET }} in your workflow YAML. Secrets are masked in logs automatically.
Organization-Level Secrets
For secrets shared across multiple repositories, use Organization Settings → Secrets. These are available to all repos in the org without duplicating configuration.
Woodpecker Secrets
Woodpecker stores secrets in its own database. Add them via the UI or CLI:
# Using Woodpecker CLI
woodpecker-cli secret add --repository your-org/your-repo \
--name docker_password \
--value 'your_registry_password'
# Or organization-wide secrets
woodpecker-cli secret add --organization your-org \
--name deploy_key \
--value @/path/to/private_key.pemPipeline Caching Strategies
npm / yarn Cache with Gitea Actions
- name: Cache node_modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-Docker Layer Cache
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build with cache
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: git.yourdomain.com/org/app:latest
cache-from: type=registry,ref=git.yourdomain.com/org/app:buildcache
cache-to: type=registry,ref=git.yourdomain.com/org/app:buildcache,mode=maxNotifications & Status Badges
Status Badge in README
# For Gitea Actions, add to README.md:
[](https://git.yourdomain.com/your-org/your-repo/actions)
# For Woodpecker CI:
[](https://ci.yourdomain.com/your-org/your-repo)Pipeline Failure Notifications (Woodpecker)
# Woodpecker: Notify via webhook on failure
notify:
image: plugins/slack
settings:
webhook:
from_secret: slack_webhook
channel: dev-alerts
template: >
{{#success build.status}}Build succeeded for {{repo.name}}{{else}}Build FAILED for {{repo.name}} on {{build.branch}}{{/success}}
when:
status: [failure, success]What's Next
Part 3 covers setting up Gitea's built-in package registries for Docker images, npm packages, and PyPI packages — all hosted on your RamNode VPS without any third-party registry fees.
