Team Workflows & Production Hardening
PR review workflows, CODEOWNERS, branch protection, project boards, and a complete security hardening checklist.
This is Part 5 of the Gitea DevOps Pipeline Series. If you are starting here, Parts 1 through 4 cover installation, CI/CD, package registries, and auto-deployment. The settings in this guide build on a running Gitea instance and are independent of the PaaS integration.
Pull Request Review Workflows
Configuring Required Reviews
Navigate to Repository → Settings → Branches to configure branch protection:
# You configure these through the Gitea UI, not config files.
# Repository > Settings > Branches > Add Branch Protection Rule
# Rule for: main
# Required approvals before merging: 1 (or more for larger teams)
# Dismiss stale approvals when new commits are pushed: checked
# Require review from code owners: checked (if using CODEOWNERS)
# Block merge on rejected reviews: checked
# Require status checks to pass: checked
# Status checks: your CI workflow name (e.g., 'test')
# Block force pushes: checked
# Block deletion of protected branch: checkedCODEOWNERS File
Gitea supports CODEOWNERS files, which automatically request reviews from specific users or teams when files they own are changed in a PR:
# Global rule: all files require review from the platform team
* @your-org/platform-team
# Infrastructure and CI files require DevOps review
/.gitea/ @your-org/devops
/docker-compose*.yml @your-org/devops
/Dockerfile* @your-org/devops
# API files require backend team review
/src/api/ @your-org/backend
# Frontend files require frontend team review
/src/frontend/ @your-org/frontend
# Security-sensitive files require security team
/src/auth/ @alice @bob
/.env.example @aliceNote: The CODEOWNERS file only triggers review requests — it does not block merging unless combined with 'Require review from code owners' in branch protection.
PR & Issue Templates
PR Template
## Summary
What does this PR do? (1-2 sentences)
## Changes
- List the key changes
- Be specific about what was added, removed, or modified
## Testing
How was this tested?
- [ ] Unit tests added or updated
- [ ] Tested manually on staging
- [ ] No test coverage needed (explain why)
## Checklist
- [ ] Documentation updated
- [ ] Environment variables documented in .env.example
- [ ] No debug logging left in code
## Related Issues
Closes #ISSUE_NUMBERIssue Template
---
name: Bug Report
about: Report unexpected behavior
labels: bug
---
**Describe the bug**
A clear description of what the bug is.
**Steps to Reproduce**
1. Go to '...'
2. Click on '...'
3. Observe error
**Expected behavior**
What should have happened?
**Environment**
- OS: [e.g. Ubuntu 22.04]
- Version: [e.g. v1.2.3]Branch & Workflow Strategies
Gitflow on Gitea
For teams managing multiple environments, a simplified Gitflow strategy works well with the CI/CD pipeline built in Parts 2 and 4:
- • main: Protected, requires PR + CI pass + 1 review. Auto-deploys to production.
- • develop: Protected, requires CI pass. Auto-deploys to staging.
- • feature/*: Short-lived branches for individual features. Open PRs targeting develop.
- • hotfix/*: Emergency fixes branched from main, merged to both main and develop.
- • release/*: Optional release preparation branch for changelog and versioning.
Merge Strategies
Configure allowed merge strategies per branch in Repository Settings → Merge Strategies. For clean history, restrict main to squash merges only:
# Branch protection for main:
# Allowed merge strategies: Squash only
# This forces all PRs to become a single commit on main,
# making git log readable and revert operations simple.
# For develop, allow merge commits to preserve PR history:
# Allowed merge strategies: Merge commit, SquashProject Boards & Milestones
Creating a Project Board
Gitea project boards are kanban-style boards that can aggregate issues from across multiple repositories:
- 1. Create a new Project under the organization
- 2. Add columns: Backlog, In Progress, In Review, Done
- 3. Link repositories: Project Settings → Linked Repositories
- 4. Add issues to the board by assigning the project label in any linked repository
Milestones & Releases
Use milestones to group issues by release version. Automate release creation:
# Automate release creation via Gitea Actions
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
run: |
git log $(git describe --tags --abbrev=0 HEAD^)..HEAD \
--pretty=format:'- %s (%an)' > CHANGELOG_LATEST.md
- name: Create release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
with:
tag_name: ${{ gitea.ref_name }}
release_name: Release ${{ gitea.ref_name }}
body_path: CHANGELOG_LATEST.mdProduction Security Hardening
1. Disable Open Registration
Unless you intend Gitea to be publicly accessible for self-signup, disable open registration immediately after setup:
# In /opt/gitea/config/app.ini
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true ; require login to view any content
# Restart Gitea to apply
cd /opt/gitea && docker compose restart gitea2. Configure Rate Limiting & Security
[api]
ENABLE_SWAGGER = false ; disable Swagger UI in production
MAX_RESPONSE_ITEMS = 50
[security]
LOGIN_REMEMBER_DAYS = 7
MIN_PASSWORD_LENGTH = 16
PASSWORD_COMPLEXITY = lower,upper,digit,spec
[oauth2]
ENABLE = true
[server]
# Limit access to admin-only API endpoints
# by reverse proxy rules (see Nginx section below)3. Nginx Security Headers
# Add to your Nginx server block for Gitea:
server {
# ... existing proxy config ...
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection '1; mode=block';
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header Permissions-Policy 'camera=(), microphone=(), geolocation=()';
# Block access to internal endpoints from the public internet
location /api/internal/ {
deny all;
}
}4. Fail2Ban for Brute Force Protection
sudo apt install fail2ban -y
# Create a Gitea jail
sudo tee /etc/fail2ban/jail.d/gitea.conf << 'EOF'
[gitea]
enabled = true
filter = gitea
logpath = /opt/gitea/logs/gitea.log
maxretry = 5
findtime = 3600
bantime = 86400
action = iptables-allports
EOF
sudo tee /etc/fail2ban/filter.d/gitea.conf << 'EOF'
[Definition]
failregex = .*(Failed authentication attempt|invalid credentials|Attempted access).* for
ignoreregex =
EOF
sudo systemctl restart fail2ban5. Two-Factor Authentication
Enforce 2FA for all admin accounts and optionally all users. Gitea supports TOTP-based 2FA:
- 1. Each user enables 2FA in User Settings → Security → Two-Factor Authentication
- 2. For admins, enforce via Admin Panel → Users → select user → 'Prohibit Login Without 2FA'
- 3. Gitea does not yet have a global 'require 2FA for all users' setting; use LDAP/OIDC integration for centrally managed 2FA enforcement
6. Regular Security Updates
#!/bin/bash
# Run monthly or when security advisories are published
cd /opt/gitea
# Pull latest image
docker compose pull gitea
# Recreate container with new image
docker compose up -d --force-recreate gitea
# Verify Gitea is healthy
sleep 10
curl -s https://git.yourdomain.com/api/v1/version | jq '.version'Monitoring Your Gitea Instance
Built-in Prometheus Metrics
Gitea exposes Prometheus metrics at /metrics when enabled:
# app.ini
[metrics]
ENABLED = true
TOKEN = a_secret_metrics_token ; protect the endpoint
# Prometheus scrape config (prometheus.yml):
scrape_configs:
- job_name: gitea
bearer_token: a_secret_metrics_token
static_configs:
- targets: ['git.yourdomain.com']
metrics_path: /metricsLog Management
# Tail live Gitea logs
docker logs -f gitea --since 1h
# Rotate and archive logs (via logrotate)
sudo tee /etc/logrotate.d/gitea << 'EOF'
/opt/gitea/logs/*.log {
daily
rotate 14
compress
missingok
notifempty
}
EOFGitea Administration Checklist
| Hardening Task | Status |
|---|---|
| Open registration disabled | ☐ Complete |
| Admin account has 2FA enabled | ☐ Complete |
| SSH key auth only (password auth disabled) | ☐ Complete |
| Fail2Ban configured for SSH and web login | ☐ Complete |
| Nginx security headers in place | ☐ Complete |
| Automated nightly backups running | ☐ Complete |
| Gitea auto-update script scheduled | ☐ Complete |
| Prometheus metrics enabled and scraped | ☐ Complete |
| Log rotation configured | ☐ Complete |
| Branch protection on main + develop | ☐ Complete |
| CODEOWNERS file in all critical repos | ☐ Complete |
| CI status checks required before merge | ☐ Complete |
| PATs rotated (schedule: every 90 days) | ☐ Complete |
| Registry cleanup cron job running | ☐ Complete |
🎉 Series Complete: What You Have Built
- ✅ Part 1: Gitea with PostgreSQL, organizations, SSH keys, and GitHub mirroring
- ✅ Part 2: CI/CD with Gitea Actions or Woodpecker CI
- ✅ Part 3: Package registry for Docker, npm, PyPI, and Helm
- ✅ Part 4: Auto-deploy on push via Dokploy or Coolify
- ✅ Part 5: Team workflows, CODEOWNERS, security hardening, and monitoring
You're now running the equivalent of GitHub + GitHub Actions + Docker Hub + Vercel — all on a single VPS.
