Some checks failed
- Delete .gitlab-ci.yml (replaced by .gitea/workflows/ci.yml) - Delete docs/deployment/gitlab.md (superseded by gitea.md) - Update audit rules to reference .gitea/workflows/*.yml - Update validate_audit.py to check Gitea CI paths - Clean up GitLab references in gitea.md, mkdocs.yml, .dockerignore - Mark IPv6 AAAA records as completed in hetzner docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
295 lines
8.3 KiB
Markdown
295 lines
8.3 KiB
Markdown
# Gitea CI/CD Deployment Guide
|
|
|
|
This document describes how to **self-host Gitea** on an external server with **Gitea Actions** CI/CD (GitHub Actions-compatible).
|
|
|
|
---
|
|
|
|
## Why Gitea?
|
|
|
|
- Lightweight, self-hosted Git forge (single binary or Docker image)
|
|
- Built-in CI/CD via **Gitea Actions** (GitHub Actions-compatible YAML)
|
|
- Built-in migration tool imports repos, issues, and PRs from other forges
|
|
- Low resource usage
|
|
|
|
---
|
|
|
|
## 1. Server Prerequisites
|
|
|
|
```bash
|
|
# Ubuntu/Debian
|
|
sudo apt update && sudo apt install -y docker.io docker-compose-plugin
|
|
sudo systemctl enable --now docker
|
|
sudo usermod -aG docker $USER # log out/in after this
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Gitea Server Setup (Docker Compose)
|
|
|
|
Create a directory on your server:
|
|
|
|
```bash
|
|
mkdir -p ~/gitea && cd ~/gitea
|
|
```
|
|
|
|
Create `docker-compose.yml`:
|
|
|
|
```yaml
|
|
services:
|
|
gitea:
|
|
image: gitea/gitea:latest
|
|
container_name: gitea
|
|
restart: always
|
|
environment:
|
|
- USER_UID=1000
|
|
- USER_GID=1000
|
|
- GITEA__database__DB_TYPE=postgres
|
|
- GITEA__database__HOST=gitea-db:5432
|
|
- GITEA__database__NAME=gitea
|
|
- GITEA__database__USER=gitea
|
|
- GITEA__database__PASSWD=<CHANGE_ME>
|
|
- GITEA__server__ROOT_URL=https://git.yourdomain.com/
|
|
- GITEA__server__SSH_DOMAIN=git.yourdomain.com
|
|
- GITEA__server__DOMAIN=git.yourdomain.com
|
|
- GITEA__actions__ENABLED=true
|
|
volumes:
|
|
- gitea-data:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
ports:
|
|
- "3000:3000" # Web UI
|
|
- "2222:22" # SSH (Git over SSH)
|
|
depends_on:
|
|
gitea-db:
|
|
condition: service_healthy
|
|
|
|
gitea-db:
|
|
image: postgres:15
|
|
container_name: gitea-db
|
|
restart: always
|
|
environment:
|
|
POSTGRES_DB: gitea
|
|
POSTGRES_USER: gitea
|
|
POSTGRES_PASSWORD: <CHANGE_ME>
|
|
volumes:
|
|
- gitea-db-data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
# Gitea Actions runner — executes CI workflows
|
|
gitea-runner:
|
|
image: gitea/act_runner:latest
|
|
container_name: gitea-runner
|
|
restart: always
|
|
environment:
|
|
GITEA_INSTANCE_URL: http://gitea:3000
|
|
GITEA_RUNNER_REGISTRATION_TOKEN: <RUNNER_TOKEN>
|
|
GITEA_RUNNER_NAME: default-runner
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- gitea-runner-data:/data
|
|
depends_on:
|
|
- gitea
|
|
|
|
volumes:
|
|
gitea-data:
|
|
gitea-db-data:
|
|
gitea-runner-data:
|
|
```
|
|
|
|
!!! warning "Replace placeholders"
|
|
Replace `<CHANGE_ME>` with a strong database password and `<RUNNER_TOKEN>` with the token from step 4.
|
|
|
|
Start Gitea and the database first:
|
|
|
|
```bash
|
|
docker compose up -d gitea gitea-db
|
|
```
|
|
|
|
Visit `http://your-server-ip:3000` and complete the initial setup wizard.
|
|
|
|
---
|
|
|
|
## 3. Reverse Proxy with HTTPS (Nginx + Let's Encrypt)
|
|
|
|
```bash
|
|
sudo apt install -y nginx certbot python3-certbot-nginx
|
|
```
|
|
|
|
Create `/etc/nginx/sites-available/gitea`:
|
|
|
|
```nginx
|
|
server {
|
|
server_name git.yourdomain.com;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
client_max_body_size 100M;
|
|
}
|
|
}
|
|
```
|
|
|
|
Enable and get a certificate:
|
|
|
|
```bash
|
|
sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
|
|
sudo certbot --nginx -d git.yourdomain.com
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Enable Actions & Register the Runner
|
|
|
|
1. Go to **Site Administration > Runners** in the Gitea web UI.
|
|
2. Click **Create new Runner** and copy the registration token.
|
|
3. Paste the token into `docker-compose.yml` as `GITEA_RUNNER_REGISTRATION_TOKEN`.
|
|
4. Start the runner:
|
|
|
|
```bash
|
|
docker compose up -d gitea-runner
|
|
```
|
|
|
|
Verify the runner appears as **Online** in the admin panel.
|
|
|
|
---
|
|
|
|
## 5. Migrate Your Repository
|
|
|
|
### Option A: Git push (code only)
|
|
|
|
```bash
|
|
# On your local machine
|
|
cd /path/to/letzshop-product-import
|
|
git remote add gitea ssh://git@git.yourdomain.com:2222/your-username/letzshop-product-import.git
|
|
git push gitea --all
|
|
git push gitea --tags
|
|
```
|
|
|
|
### Option B: Gitea built-in migration (code + issues + PRs)
|
|
|
|
1. In Gitea, click **+** > **New Migration**.
|
|
2. Select the source forge (GitHub, GitLab, etc.).
|
|
3. Enter the source URL and a Personal Access Token.
|
|
4. Gitea will import the repository, issues, labels, milestones, and pull/merge requests.
|
|
|
|
---
|
|
|
|
## 6. CI/CD — Gitea Actions
|
|
|
|
The workflow file lives in `.gitea/workflows/ci.yml` (already created in this repository). Gitea Actions uses GitHub Actions-compatible YAML syntax.
|
|
| `artifacts: paths:` | `actions/upload-artifact@v4` (not supported on Gitea GHES) |
|
|
| `cache: paths:` | `actions/cache@v4` |
|
|
| `coverage: '/regex/'` | Use coverage action or parse in step |
|
|
| CI/CD Variables (UI) | Repository **Settings > Secrets** |
|
|
|
|
---
|
|
|
|
## 7. CI/CD Secrets
|
|
|
|
Configure these in your Gitea repository under **Settings > Actions > Secrets**:
|
|
|
|
| Secret | Description | Used by |
|
|
|--------|-------------|---------|
|
|
| `DEPLOY_SSH_KEY` | Ed25519 private key for deployment | Deploy job |
|
|
| `DEPLOY_HOST` | Docker bridge gateway IP: `172.17.0.1` (see note below) | Deploy job |
|
|
| `DEPLOY_USER` | SSH user on production server (e.g. `samir`) | Deploy job |
|
|
| `DEPLOY_PATH` | App directory on server (e.g. `/home/samir/apps/orion`) | Deploy job |
|
|
|
|
!!! important "DEPLOY_HOST must be `172.17.0.1`, not `127.0.0.1`"
|
|
The Gitea Actions runner executes CI jobs inside Docker containers. From inside the container, `127.0.0.1` refers to the container itself, not the host machine. Use `172.17.0.1` (the Docker bridge gateway) so the SSH action can reach the host's SSH daemon. When Gitea and Orion are split onto separate servers, update this to the Orion server's real IP.
|
|
|
|
---
|
|
|
|
## 8. Pipeline Overview
|
|
|
|
The CI pipeline (`.gitea/workflows/ci.yml`) runs:
|
|
|
|
```
|
|
push/PR to master
|
|
├── ruff (lint)
|
|
├── pytest (tests + PostgreSQL service)
|
|
├── validate (all 4 validators: architecture, security, performance, audit)
|
|
├── dependency-scanning (pip-audit, non-blocking)
|
|
├── docs (mkdocs build, master-only, after lint+test+validate pass)
|
|
└── deploy (SSH deploy, master-only, after lint+test+validate pass)
|
|
```
|
|
|
|
All jobs run in parallel except `docs` and `deploy`, which wait for `ruff`, `pytest`, and `validate` to pass. The `deploy` job only runs on push (not PRs).
|
|
|
|
---
|
|
|
|
## 9. Deploy Job (Continuous Deployment)
|
|
|
|
The CI pipeline includes an automated deploy job that runs on every successful push to master. It SSHes to the production server and runs a version-controlled deploy script:
|
|
|
|
```yaml
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
needs: [ruff, pytest, validate]
|
|
steps:
|
|
- name: Deploy to production
|
|
uses: appleboy/ssh-action@v1
|
|
with:
|
|
host: ${{ secrets.DEPLOY_HOST }}
|
|
username: ${{ secrets.DEPLOY_USER }}
|
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
|
port: 22
|
|
command_timeout: 10m
|
|
script: cd ${{ secrets.DEPLOY_PATH }} && bash scripts/deploy.sh
|
|
```
|
|
|
|
The `scripts/deploy.sh` script handles the full deploy lifecycle:
|
|
|
|
1. Stash local changes (preserves `.env` and other server-side edits)
|
|
2. Pull latest code (`--ff-only`)
|
|
3. Pop stash to restore local changes
|
|
4. Rebuild and restart Docker containers (`docker compose --profile full up -d --build`)
|
|
5. Run database migrations (`alembic upgrade heads`)
|
|
6. Health check `http://localhost:8001/health` with retries
|
|
|
|
See [Hetzner Server Setup — Step 16](hetzner-server-setup.md#step-16-continuous-deployment) for the full setup guide including SSH key generation and Gitea secrets configuration.
|
|
|
|
---
|
|
|
|
## 10. Firewall Configuration
|
|
|
|
```bash
|
|
sudo ufw allow OpenSSH
|
|
sudo ufw allow 'Nginx Full'
|
|
# Allow CI containers (Docker bridge) to SSH to the host for deployment
|
|
sudo ufw allow from 172.17.0.0/16 to any port 22
|
|
sudo ufw enable
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Maintenance
|
|
|
|
```bash
|
|
# View Gitea logs
|
|
docker compose -f ~/gitea/docker-compose.yml logs -f gitea
|
|
|
|
# View runner logs
|
|
docker compose -f ~/gitea/docker-compose.yml logs -f gitea-runner
|
|
|
|
# Update Gitea
|
|
cd ~/gitea
|
|
docker compose pull
|
|
docker compose up -d
|
|
|
|
# Backup Gitea data
|
|
docker run --rm -v gitea-data:/data -v $(pwd):/backup alpine \
|
|
tar czf /backup/gitea-backup-$(date +%Y%m%d).tar.gz /data
|
|
```
|
|
|
|
---
|