gitea
Installation
SKILL.md
Identity
- Deployment: single binary (native) or Docker image (
gitea/gitea,codeberg.org/forgejo/forgejo) - Unit:
gitea.service(native) — no systemd unit in Docker deployments - Config:
/etc/gitea/app.ini(native) or/data/gitea/conf/app.ini(Docker volume) - Data dir:
/var/lib/gitea(native) or/data(Docker volume) - Logs:
journalctl -u gitea, or/var/lib/gitea/log///data/gitea/log/ - Git user:
git(native install) — all SSH operations run as this user - Ports: 3000 (HTTP web UI), 22 or 2222 (SSH git operations)
- Forgejo: community fork of Gitea with identical
app.iniformat and CLI — all commands below work on both. Replacegiteabinary withforgejowhere applicable.
Key Operations
| Operation | Command |
|---|---|
| Service status | systemctl status gitea |
| Start / stop / restart | systemctl start|stop|restart gitea |
| Reload config (no restart) | systemctl reload gitea — note: most [server] changes require a full restart |
| Admin CLI help | gitea admin --help (or forgejo admin --help) |
| Create first admin user | gitea admin user create --username admin --password <pass> --email admin@example.com --admin |
| Reset a user's password | gitea admin user change-password --username <user> --password <newpass> |
| Regenerate git hooks | gitea admin regenerate hooks — fixes broken push/post-receive hooks after upgrade |
| Regenerate SSH keys | gitea admin regenerate keys — rebuilds ~git/.ssh/authorized_keys |
| List all users | gitea admin user list |
| Create a repository | gitea admin repo create --owner <user> --name <repo> |
| Migrate remote repo | gitea admin repo migrate --url https://github.com/owner/repo --owner <localuser> --name <repo> |
| Backup (full dump) | gitea dump -c /etc/gitea/app.ini -t /tmp — produces a .zip with repos, DB, config, attachments |
| Restore from dump | Extract zip, then gitea restore --config /etc/gitea/app.ini with the extracted path |
| Run DB migrations | gitea migrate — required before starting a newer binary on an older database |
| Show effective config | gitea dump-config -c /etc/gitea/app.ini — prints merged config with defaults resolved |
| Check app.ini syntax | gitea dump-config exits non-zero on parse errors; also try gitea web --dry-run if available |
| List OAuth2 applications | gitea admin auth list — shows all external auth sources including OAuth2 apps |
| Delete auth source | gitea admin auth delete --id <id> |
Expected Ports
- 3000/tcp: HTTP web UI (or custom
HTTP_PORTin[server]) - 22/tcp: SSH git (native, using system SSH and
authorized_keyspassthrough) - 2222/tcp: SSH git (Docker, mapped from internal 22 to avoid host port conflict)
- Verify:
ss -tlnp | grep -E '3000|2222|:22' - Firewall:
sudo ufw allow 3000/tcp && sudo ufw allow 2222/tcp
Health Checks
systemctl is-active gitea→active(native installs only)curl -sI http://localhost:3000→ HTTP 200 or 302 (not connection refused)curl -s http://localhost:3000/api/healthz→{"status":"pass"}(Gitea 1.16+)
Common Failures
| Symptom | Likely cause | Check / Fix |
|---|---|---|
| "There is no user with that email" on first web setup | No admin user exists yet | Run gitea admin user create --admin from the CLI before opening the web installer |
SSH key accepted but git clone returns "Permission denied" |
authorized_keys not rebuilt after install or upgrade |
Run gitea admin regenerate keys; check ~git/.ssh/authorized_keys contains entries |
| SSH works locally but not through reverse proxy | SSH passthrough not configured — proxy only forwards HTTP | Use a dedicated TCP proxy for port 22/2222, or configure SSH_DOMAIN and map Docker port directly |
| LFS pushes succeed but objects return 404 on fetch | LFS server not enabled or storage misconfigured | Set LFS_START_SERVER = true in [server]; confirm LFS_CONTENT_PATH is writable |
| HTTPS OAuth / webhook callbacks fail | ROOT_URL does not match actual public URL |
Set ROOT_URL = https://git.example.com/ exactly — trailing slash required; mismatch breaks redirects and HMAC signatures |
| Database migration fails on upgrade | Newer binary run before gitea migrate |
Stop service, run gitea migrate -c /etc/gitea/app.ini, then start new binary |
| Email notifications not sent | SMTP unconfigured or ENABLED = false in [mailer] |
Set ENABLED = true, correct SMTP_ADDR, FROM, USER, PASSWD; test with gitea admin sendmail |
| Webhook deliveries show "connection refused" | Webhook target is localhost and ALLOW_LOCAL_NETWORKS is false |
Set ALLOW_LOCAL_NETWORKS = true in [webhook] for internal targets, or use the real external URL |
Pain Points
ROOT_URLmust be exact: include protocol, domain, port (if non-standard), and a trailing slash. OAuth2 redirect URIs, webhook HMAC signing, and avatar URLs are all derived from this value. A mismatch causes silent partial failures.- SSH passthrough via
authorized_keys: native installs use thegitsystem user; every registered SSH key is written to~git/.ssh/authorized_keyswith acommand=wrapper pointing at the Gitea binary. Docker deployments avoid this complexity by exposing port 2222 directly — no hostgituser needed. gitea migratebefore binary upgrade: Gitea does not auto-migrate on startup. Running a newer binary against an unmigrated database produces confusing errors. Always stop, migrate, then start.- LFS requires separate storage config: Git LFS is not enabled by default. It requires
LFS_START_SERVER = trueand a configuredLFS_CONTENT_PATH(or S3/Minio object storage for larger deployments). - Forgejo is a drop-in replacement: all
app.inikeys, Docker image layout, and CLI commands are identical. Replacegiteabinary/image withforgejoequivalent; no config changes required. Forgejo may diverge in future versions — check its changelog before assuming parity. - Docker volume layout differs from native: inside the container
/data/gitea/conf/app.iniis the config,/data/git/repositoriesholds repos. Mount/dataas a single volume or map subdirectories individually — mixing strategies causes permission issues. INSTALL_LOCK = truemust be set before first start in automated deployments: without it, any unauthenticated visitor who reaches the install page can become the first admin. Set it inapp.inibefore exposing the port.
References
See references/ for:
app.ini.annotated— full app.ini with every directive explaineddocs.md— official documentation links
Related skills