Skip to content

Deploy Gitea + Runners

Gitea is a lightweight self-hosted Git platform. With Gitea Actions (GitHub Actions-compatible), you get code hosting + CI in one place.

  • Gitea at git.example.com with TLS
  • PostgreSQL as database
  • 2 Actions runners for CI
  • Backup of the gitea_data volume + postgres dump

Stacks → New stack → name gitea:

services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: gitea
POSTGRES_USER: gitea
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db:/var/lib/postgresql/data
gitea:
image: gitea/gitea:1.22
restart: unless-stopped
depends_on: [db]
environment:
USER_UID: 1000
USER_GID: 1000
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: db:5432
GITEA__database__NAME: gitea
GITEA__database__USER: gitea
GITEA__database__PASSWD: ${DB_PASSWORD}
GITEA__server__DOMAIN: git.example.com
GITEA__server__ROOT_URL: https://git.example.com/
GITEA__server__SSH_DOMAIN: git.example.com
GITEA__service__DISABLE_REGISTRATION: "true"
GITEA__actions__ENABLED: "true"
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "2222:22" # SSH clone
runner-1:
image: gitea/act_runner:latest
restart: unless-stopped
depends_on: [gitea]
environment:
GITEA_INSTANCE_URL: http://gitea:3000
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
GITEA_RUNNER_NAME: runner-1
volumes:
- runner1_data:/data
- /var/run/docker.sock:/var/run/docker.sock
runner-2:
image: gitea/act_runner:latest
restart: unless-stopped
depends_on: [gitea]
environment:
GITEA_INSTANCE_URL: http://gitea:3000
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
GITEA_RUNNER_NAME: runner-2
volumes:
- runner2_data:/data
- /var/run/docker.sock:/var/run/docker.sock
volumes:
db:
gitea_data:
runner1_data:
runner2_data:
  • DB_PASSWORD — random 32 chars
  • RUNNER_TOKEN — generated from Gitea admin UI after initial setup (leave blank for first deploy, set later)

Comment out the two runner services for the first deploy. Gitea needs to be up before you can generate runner tokens.

Deploy. Visit http://your-host:3000 (no TLS yet — we’ll add it next).

Complete the initial setup wizard:

  • Keep the pre-filled database settings
  • Admin username + password
  • Click Install

Stack → Proxy → Add route:

FieldValue
Domaingit.example.com
Target containergitea_gitea_1
Target port3000
TLSAutomatic

Caddy provisions the cert. Update GITEA__server__ROOT_URL to https://git.example.com/ and restart the gitea container.

  1. Log in to Gitea as admin
  2. Site Administration → Actions → Runners → Create Runner
  3. Copy the registration token
  4. Set RUNNER_TOKEN in the stack’s environment
  5. Uncomment the two runner services
  6. Deploy

Runners register within 10 seconds. Verify under Site Administration → Actions → Runners.

Create a repo with .gitea/workflows/test.yml:

name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Hello from $RUNNER_NAME"

Push. Gitea routes the job to one of the runners. Watch it complete in the Actions tab.

Standard PostgreSQL-stack backup:

Backups → Jobs → New job:

  • Stacks: gitea
  • Target: your off-site target
  • Schedule: Daily 02:00
  • Pre-backup hook: PostgreSQL · pg_dumpall
  • Retention: Keep last 30

gitea_data includes all repos, user avatars, and the internal SQLite for some features. Worth backing up even with pg_dumpall.

Gitea’s SSH is exposed on port 2222 (to avoid conflicting with your host’s SSH on 22). Clone via:

git clone ssh://git@git.example.com:2222/user/repo.git

If you want port 22, remove your host’s sshd from that port (or move it), map "22:22" in the gitea service, and open port 22 on the firewall.