- Extract login/dashboard from billing module into core (matching admin pattern) - Add merchant auth API with path-isolated cookies (path=/merchants) - Add merchant base layout with sidebar/header partials and Alpine.js init - Add frontend detection and login redirect for MERCHANT type - Wire merchant token in shared api-client.js (get/clear) - Migrate billing templates to merchant base with dark mode support - Fix Tailwind: rename shop→storefront in sources and config - DRY Makefile tailwind targets with TAILWIND_FRONTENDS loop - Rebuild all Tailwind outputs (production minified) - Add Gitea Actions CI workflow (ruff, pytest, architecture, docs) - Add Gitea deployment documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.9 KiB
Gitea CI/CD Deployment Guide
This document describes how to self-host Gitea on an external server and migrate CI/CD from GitLab to Gitea Actions (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 GitLab
- Low resource usage compared to GitLab
1. Server Prerequisites
# 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:
mkdir -p ~/gitea && cd ~/gitea
Create docker-compose.yml:
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:
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)
sudo apt install -y nginx certbot python3-certbot-nginx
Create /etc/nginx/sites-available/gitea:
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:
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
- Go to Site Administration > Runners in the Gitea web UI.
- Click Create new Runner and copy the registration token.
- Paste the token into
docker-compose.ymlasGITEA_RUNNER_REGISTRATION_TOKEN. - Start the runner:
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)
# 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)
- In Gitea, click + > New Migration.
- Select GitLab as the source.
- Enter your GitLab URL and a Personal Access Token.
- Gitea will import the repository, issues, labels, milestones, and merge requests.
6. CI/CD — GitLab vs Gitea Actions
The workflow file lives in .gitea/workflows/ci.yml (already created in this repository).
GitLab CI (.gitlab-ci.yml) |
Gitea Actions (.gitea/workflows/ci.yml) |
|---|---|
stages: + stage: per job |
Jobs run in parallel; use needs: for ordering |
services: (top-level on job) |
services: nested under each job with options: |
allow_failure: true |
continue-on-error: true |
rules: - if: |
on: triggers + if: conditionals per job |
artifacts: paths: |
actions/upload-artifact@v4 |
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 |
|---|---|---|
SSH_PRIVATE_KEY |
Private key for deployment server | Deploy job (if added) |
SERVER_HOST |
Production server IP/hostname | Deploy job |
SERVER_USER |
SSH user on production server | Deploy job |
SERVER_PATH |
App directory on server | Deploy job |
8. Pipeline Overview
The CI pipeline (.gitea/workflows/ci.yml) runs:
push/PR to master
├── ruff (lint)
├── pytest (tests + PostgreSQL service)
├── architecture (architecture validation)
├── dependency-scanning (pip-audit, non-blocking)
├── audit (custom audit, non-blocking)
└── docs (mkdocs build, master-only, after lint+test pass)
All jobs run in parallel except docs, which waits for ruff, pytest, and architecture to pass.
9. Adding a Deploy Job (Optional)
To add automated deployment via SSH (similar to the GitLab deploy stage), add this job to .gitea/workflows/ci.yml:
deploy:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs: [ruff, pytest, architecture]
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ${{ secrets.SERVER_PATH }}
git pull origin master
source .venv/bin/activate
uv sync --frozen
python -m alembic upgrade head
sudo systemctl restart wizamart
10. Firewall Configuration
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
11. Maintenance
# 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
12. Removing GitLab (After Migration)
Once you have verified everything works on Gitea:
- Update your local git remote:
git remote set-url origin ssh://git@git.yourdomain.com:2222/your-username/letzshop-product-import.git - The
.gitlab-ci.ymlfile can be removed from the repository. - Archive or delete the GitLab project.