1. Architecture Overview
Gitea is a self-hosted Git forge written in Go that provides repository hosting, issue tracking, pull requests, CI/CD (Gitea Actions), a built-in package registry, and extensive authentication support — all compiled into a single binary. Forked from Gogs, Gitea has evolved into a fully independent project with a mature plugin and extension architecture.
Gitea is designed for minimal operational overhead: a single binary, an optional external database, and a configuration file are all that is required. It embeds web assets, templates, locale files, and default configurations directly into the binary (when built with the bindata tag), enabling zero-dependency deployment.
Architecture Diagram
┌──────────────────────────────────────────┐
│ Gitea Binary │
│ │
HTTP/HTTPS ──────────┤ ┌─────────┐ ┌──────────┐ │
(go-chi) │ │ Routers │──▶│ Services │ │
│ │ (web, │ │ (pull, │ │
SSH ─────────────────┤ │ api, │ │ issue, │ │
(built-in/OpenSSH) │ │ private│ │ auth, │ │
│ │ install│ │ repo, │ │
│ └────┬────┘ │ webhook,│ │
│ │ │ mailer, │ │
│ ▼ │ actions)│ │
│ ┌─────────┐ └────┬─────┘ │
│ │ Models │◀───────┘ │
│ │ (XORM) │ │
│ └────┬────┘ │
│ │ ┌───────────┐ │
│ ▼ │ Modules │ │
│ ┌─────────┐ │ (git, │ │
│ │Database │ │ setting, │ │
│ │(PG/MySQL│ │ markup, │ │
│ │/SQLite/ │ │ storage, │ │
│ │ MSSQL) │ │ queue, │ │
│ └─────────┘ │ indexer, │ │
│ │ cache, │ │
│ │ log) │ │
│ └───────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ Embedded Resources │ │
│ │ templates/ options/ web_src/ │ │
│ └───────────────────────────────────┘ │
└──────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Object Store │ │ Git Repos │ │ Indexer │
│ (local/S3/ │ │ (filesystem)│ │(bleve/ES/ │
│ Azure Blob) │ │ │ │ meilisearch) │
└──────────────┘ └──────────────┘ └──────────────┘
Component Inventory
| Component | Path | Role |
|---|---|---|
| CLI entry point | main.go → cmd/ |
Binary entry, subcommand dispatch (urfave/cli v3) |
| Web routers | routers/web/ |
HTTP request routing (go-chi/chi v5) |
| API routers | routers/api/ |
REST API (Swagger-documented) and package registry |
| Private API | routers/private/ |
Internal API for SSH → web communication |
| Install router | routers/install/ |
First-run setup wizard |
| Services | services/ |
Business logic (pull, issue, auth, webhook, mailer, actions, etc.) |
| Models | models/ |
Data access layer (XORM ORM, database-agnostic) |
| Modules | modules/ |
Shared utilities (git, setting, markup, storage, queue, indexer, etc.) |
| Templates | templates/ |
Go HTML templates for the web UI |
| Frontend | web_src/ |
JavaScript, CSS, Vue components |
| Options | options/ |
Embedded resources (locales, labels, licenses, gitignore templates, READMEs) |
| Contrib | contrib/ |
Service manager configs (systemd, supervisor, launchd), IDE configs |
| Docker | docker/ |
Dockerfile and rootless variant |
| Snap | snap/ |
Snap package definition |
Technology Stack
| Layer | Technology |
|---|---|
| Language | Go 1.26+ |
| HTTP Router | go-chi/chi v5 |
| CLI | urfave/cli v3 |
| ORM | XORM |
| Template Engine | Go html/template |
| Frontend | Vanilla JS, Fomantic UI, Vue.js (selective), TypeScript |
| Markup Rendering | goldmark (Markdown), go-org (Org mode), chroma (syntax highlighting) |
| Git Integration | Native Git CLI + go-git library |
| Authentication | LDAP, OAuth2 (goth), SAML, SMTP, PAM, SSPI, WebAuthn |
| Search | bleve (built-in), Elasticsearch, Meilisearch |
| Cache | In-memory, Redis, Memcache, TwoQueue |
| Queue | Level, Channel, Redis |
| Storage | Local filesystem, MinIO/S3, Azure Blob Storage |
| Database | PostgreSQL, MySQL/MariaDB, SQLite, MSSQL |
Data Flow: Git Push
1. User pushes via SSH or HTTPS
2. SSH: built-in SSH server or OpenSSH → cmd/serv → cmd/hook
HTTPS: routers/web/githttp.go → repo.ServiceReceivePack
3. Git hooks fire: pre-receive → update → post-receive
4. Post-receive hook calls Gitea internal API (routers/private/)
5. Services layer processes: update refs, trigger webhooks, update indexer
6. Notification system dispatches: email, webhooks, UI notifications
7. If Actions workflows exist: queue CI jobs for runners
2. System Requirements
| Component | Minimum | Production Recommended |
|---|---|---|
| CPU | 1 core | 2-4 cores |
| RAM | 512 MB | 2-4 GB |
| Disk | 1 GB (binary + DB) | SSD, sized for repositories |
| Go | 1.26+ (build only) | Latest stable |
| Git | 2.0+ | Latest stable (2.40+) |
| Database | SQLite 3 | PostgreSQL 12+ |
| OS | Linux, macOS, Windows, FreeBSD | Linux (amd64/arm64) |
| Node.js | 18+ (build frontend only) | N/A for binary deployment |
Note
Gitea's Git integration shells out to the system
gitbinary. The installed Git version directly affects feature availability: LFS requires Git >= 2.1.2, certain advanced features require Git >= 2.40. Gitea validates the Git version at startup and fails fast with a clear error if the version is unsupported.
Supported Databases
| Database | Minimum Version | Notes |
|---|---|---|
| SQLite | 3 | Default, good for small instances (<50 users) |
| PostgreSQL | 12+ | Recommended for production |
| MySQL | 8.0+ | MariaDB 10.4+ also supported |
| MSSQL | 2012+ | Microsoft SQL Server |
3. Configuration Reference
Gitea reads configuration from an INI file at {WorkPath}/custom/conf/app.ini. Every setting can be overridden via environment variables with the GITEA__ prefix pattern.
Configuration Files
| File | Format | Location | Purpose |
|---|---|---|---|
app.ini |
INI | {CustomPath}/conf/app.ini |
Primary configuration |
| Environment vars | GITEA__SECTION__KEY |
Process environment | Override any app.ini setting |
{CustomPath}/conf/app.ini |
INI | Default: {WorkPath}/custom/conf/ |
Per-instance overrides |
Global CLI Flags
| Flag | Alias | Default | Description |
|---|---|---|---|
--work-path |
-w |
Binary directory | Gitea working directory |
--config |
-c |
{WorkPath}/custom/conf/app.ini |
Custom config file path |
--custom-path |
-C |
{WorkPath}/custom |
Custom file directory |
Environment Variable Override System
Gitea supports environment variable configuration with the pattern GITEA__SECTION__KEY=value. The double underscore separates section and key names. Special characters in section or key names are hex-encoded:
# [server] HTTP_PORT = 8080
GITEA__server__HTTP_PORT=8080
# [database] DB_TYPE = postgres
GITEA__database__DB_TYPE=postgres
# Hex encoding for dots: [service.explore] DISABLE_USERS_PAGE
GITEA__service_0X2E_explore__DISABLE_USERS_PAGE=true
The __FILE suffix reads the value from a file (useful for Docker secrets):
GITEA__database__PASSWD__FILE=/run/secrets/db_password
Important
Environment variables take precedence over
app.inisettings. They are applied at startup and are not written back to the config file.
[server] Section
| Key | Type | Default | Description |
|---|---|---|---|
PROTOCOL |
string | http |
Protocol: http, https, unix, fcgi, fcgi+unix |
DOMAIN |
string | localhost |
Domain for SSH URLs and email links |
HTTP_ADDR |
string | 0.0.0.0 |
Listen address |
HTTP_PORT |
int | 3000 |
Listen port |
ROOT_URL |
string | (computed) | Full external URL |
SSH_DOMAIN |
string | (same as DOMAIN) | SSH domain name |
SSH_PORT |
int | 22 |
SSH port |
START_SSH_SERVER |
bool | false |
Start built-in SSH server |
SSH_LISTEN_HOST |
string | 0.0.0.0 |
SSH listen address |
SSH_LISTEN_PORT |
int | 22 |
SSH listen port |
CERT_FILE |
string | TLS certificate path | |
KEY_FILE |
string | TLS private key path | |
LFS_START_SERVER |
bool | false |
Enable Git LFS server |
OFFLINE_MODE |
bool | false |
Disable external CDN resources |
LANDING_PAGE |
string | home |
Landing page: home, explore, organizations, login |
[database] Section
| Key | Type | Default | Description |
|---|---|---|---|
DB_TYPE |
string | sqlite3 |
Database engine: postgres, mysql, mssql, sqlite3 |
HOST |
string | 127.0.0.1:3306 |
Database host and port |
NAME |
string | gitea |
Database name |
USER |
string | root |
Database user |
PASSWD |
string | Database password | |
SSL_MODE |
string | disable |
PostgreSQL SSL mode |
LOG_SQL |
bool | false |
Log SQL queries |
[repository] Section
| Key | Type | Default | Description |
|---|---|---|---|
ROOT |
string | {AppDataPath}/gitea-repositories |
Repository storage path |
DEFAULT_BRANCH |
string | main |
Default branch name |
DEFAULT_PRIVATE |
string | last |
Default visibility: last, private, public |
ENABLE_PUSH_CREATE_USER |
bool | false |
Allow creating repos via push |
ENABLE_PUSH_CREATE_ORG |
bool | false |
Allow creating org repos via push |
DISABLED_REPO_UNITS |
string | Disabled repository units | |
DEFAULT_REPO_UNITS |
string | Default repo units for new repos |
[service] Section
| Key | Type | Default | Description |
|---|---|---|---|
DISABLE_REGISTRATION |
bool | false |
Disable user self-registration |
REQUIRE_SIGNIN_VIEW |
bool | false |
Require login to view anything |
REGISTER_EMAIL_CONFIRM |
bool | false |
Require email confirmation |
ENABLE_NOTIFY_MAIL |
bool | false |
Enable email notifications |
DEFAULT_KEEP_EMAIL_PRIVATE |
bool | false |
Hide user email by default |
ENABLE_CAPTCHA |
bool | false |
Enable CAPTCHA on registration |
[admin] Section
| Key | Type | Default | Description |
|---|---|---|---|
DISABLE_REGULAR_ORG_CREATION |
bool | false |
Only admins can create organizations |
DEFAULT_EMAIL_NOTIFICATIONS |
string | enabled |
Default email notification setting |
USER_DISABLED_FEATURES |
string | Comma-separated disabled features: deletion, manage_ssh_keys, manage_gpg_keys, manage_mfa, manage_credentials, change_username, change_full_name |
|
EXTERNAL_USER_DISABLE_FEATURES |
string | Additional disabled features for external auth users |
[actions] Section
| Key | Type | Default | Description |
|---|---|---|---|
ENABLED |
bool | true |
Enable Gitea Actions |
DEFAULT_ACTIONS_URL |
string | github |
Action resolution source: github or self |
LOG_RETENTION_DAYS |
int | 365 |
Days to retain action logs |
LOG_COMPRESSION |
string | zstd |
Log compression: zstd or none |
ARTIFACT_RETENTION_DAYS |
int | 90 |
Days to retain artifacts |
ZOMBIE_TASK_TIMEOUT |
duration | 10m |
Timeout for zombie task detection |
ENDLESS_TASK_TIMEOUT |
duration | 3h |
Timeout for endless tasks |
ABANDONED_JOB_TIMEOUT |
duration | 24h |
Timeout for abandoned jobs |
SKIP_WORKFLOW_STRINGS |
string | [skip ci],[ci skip],[no ci],[skip actions],[actions skip] |
Commit message patterns that skip workflows |
WORKFLOW_DIRS |
string | .gitea/workflows,.github/workflows |
Directories to search for workflow files |
Important
When setting
DEFAULT_ACTIONS_URL = self, action references likeuses: actions/checkout@v4resolve against your own Gitea instance instead of GitHub. Ensure the required actions are available in your instance.
[api] Section
| Key | Type | Default | Description |
|---|---|---|---|
ENABLE_SWAGGER |
bool | true |
Enable Swagger API documentation UI |
MAX_RESPONSE_ITEMS |
int | 50 |
Maximum items in paginated responses |
DEFAULT_PAGING_NUM |
int | 30 |
Default page size |
DEFAULT_MAX_BLOB_SIZE |
int64 | 10485760 |
Max blob size in API responses (10 MB) |
DEFAULT_MAX_RESPONSE_SIZE |
int64 | 104857600 |
Max total response size (100 MB) |
[attachment] Section
| Key | Type | Default | Description |
|---|---|---|---|
ENABLED |
bool | true |
Enable file attachments |
ALLOWED_TYPES |
string | .avif,.csv,.docx,... (see below) |
Allowed file extensions |
MAX_SIZE |
int64 | 100 |
Max attachment size in MB |
MAX_FILES |
int | 5 |
Max files per upload |
STORAGE_TYPE |
string | local |
Storage backend: local, minio |
Default allowed types: .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip
[cache] Section
| Key | Type | Default | Description |
|---|---|---|---|
ADAPTER |
string | memory |
Cache backend: memory, redis, memcache, twoqueue |
INTERVAL |
int | 60 |
Garbage collection interval (seconds) |
HOST |
string | Connection string for redis/memcache | |
ITEM_TTL |
duration | 16h |
Cache item TTL |
[cache.last_commit] Section
| Key | Type | Default | Description |
|---|---|---|---|
ITEM_TTL |
duration | 8760h |
Last commit cache TTL (1 year) |
COMMITS_COUNT |
int64 | 1000 |
Commits count threshold for caching |
[camo] Section
| Key | Type | Default | Description |
|---|---|---|---|
ENABLED |
bool | false |
Enable camo image proxy |
SERVER_URL |
string | Camo server URL (required if enabled) | |
HMAC_KEY |
string | HMAC key for camo (required if enabled) | |
ALWAYS |
bool | false |
Proxy all images (not just HTTP) |
[mailer] Section
| Key | Type | Description |
|---|---|---|
ENABLED |
bool | Enable email sending |
SMTP_ADDR |
string | SMTP server address |
SMTP_PORT |
int | SMTP server port |
FROM |
string | Sender email address |
USER |
string | SMTP authentication username |
PASSWD |
string | SMTP authentication password |
[storage] Section
Gitea supports pluggable storage backends for attachments, LFS, avatars, packages, and action artifacts.
| Key | Type | Default | Description |
|---|---|---|---|
STORAGE_TYPE |
string | local |
Default storage: local, minio |
PATH |
string | varies | Local storage path |
MINIO_ENDPOINT |
string | MinIO/S3 endpoint | |
MINIO_ACCESS_KEY_ID |
string | S3 access key | |
MINIO_SECRET_ACCESS_KEY |
string | S3 secret key | |
MINIO_BUCKET |
string | gitea |
S3 bucket name |
MINIO_LOCATION |
string | S3 region | |
MINIO_USE_SSL |
bool | false |
Use SSL for S3 |
SERVE_DIRECT |
bool | false |
Redirect to S3 URLs directly |
Storage inheritance: specific storage sections ([storage.attachments], [storage.lfs], etc.) inherit from [storage] unless overridden.
Tip
You can define named storage backends and reference them:
[storage.my_s3] STORAGE_TYPE = minio MINIO_ENDPOINT = s3.example.com [attachment] STORAGE_TYPE = my_s3
Key Environment Variables
| Variable | Purpose |
|---|---|
GITEA_RUN_MODE |
Run mode override (dev, prod, test) |
GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT |
Suppress root user warning |
GITEA_RUNNER_REGISTRATION_TOKEN |
Auto-register runner token |
GITEA_RUNNER_REGISTRATION_TOKEN_FILE |
Read runner token from file |
SKIP_MINWINSVC |
Skip Windows service detection |
4. Deployment & Scaling
Docker Deployment
docker-compose.yml (PostgreSQL):
version: "3"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
environment:
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea
restart: always
volumes:
- ./gitea/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
depends_on:
- db
db:
image: postgres:16
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea
- POSTGRES_DB=gitea
volumes:
- ./postgres/data:/var/lib/postgresql/data
Rootless Docker variant: Use gitea/gitea:latest-rootless for environments that prohibit running as root. The rootless image uses different default paths:
| Standard Image | Rootless Image |
|---|---|
/data |
/var/lib/gitea (data), /etc/gitea (config) |
Runs as root, drops to git |
Runs as uid 1000 throughout |
| SSH on port 22 | SSH on port 2222 |
Warning
The rootless image cannot bind to privileged ports (< 1024). Map to higher ports (e.g.,
-p 2222:2222for SSH,-p 3000:3000for HTTP).
Systemd Service
[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
After=postgresql.service
[Service]
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
[Install]
WantedBy=multi-user.target
Supervisor Configuration
[program:gitea]
directory=/var/lib/gitea/
command=/usr/local/bin/gitea web --config /etc/gitea/app.ini
autostart=true
autorestart=true
startsecs=10
stdout_logfile=/var/log/gitea/stdout.log
stderr_logfile=/var/log/gitea/stderr.log
environment=USER="git",HOME="/home/git"
user=git
macOS (launchd)
Place a plist in ~/Library/LaunchAgents/ or /Library/LaunchDaemons/ referencing the Gitea binary.
Reverse Proxy Configuration
Nginx:
server {
listen 443 ssl;
server_name git.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
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;
}
}
Note
When behind a reverse proxy, set
[server] ROOT_URLto the external URL (e.g.,https://git.example.com/) and ensure the proxy forwards theHost,X-Real-IP, andX-Forwarded-Protoheaders.
Snap Deployment
sudo snap install gitea
The snap package automatically manages service lifecycle. Configuration is at /var/snap/gitea/common/conf/app.ini.
Scaling Considerations
Gitea is designed as a single-instance application. For high availability:
- Database: Use a replicated PostgreSQL or MySQL cluster
- Storage: Use S3-compatible object storage for attachments, LFS, packages, and artifacts
- Cache: Use Redis for shared caching across potential future multi-instance setups
- Queue: Use Redis-backed queues for reliable job processing
- Search: Use Elasticsearch or Meilisearch for external indexing
5. Operations
CLI Command Reference
Gitea provides a comprehensive CLI for administration:
| Command | Purpose |
|---|---|
gitea web |
Start the web server (default command) |
gitea admin user list |
List users |
gitea admin user create |
Create a user |
gitea admin user change-password |
Change user password |
gitea admin user delete |
Delete a user |
gitea admin auth list |
List authentication sources |
gitea admin auth add-ldap |
Add LDAP (Bind DN) authentication |
gitea admin auth update-ldap |
Update LDAP (Bind DN) authentication |
gitea admin auth add-ldap-simple |
Add LDAP (simple auth) authentication |
gitea admin auth update-ldap-simple |
Update LDAP (simple auth) authentication |
gitea admin auth add-oauth |
Add OAuth2 authentication |
gitea admin auth update-oauth |
Update OAuth2 authentication |
gitea admin auth add-smtp |
Add SMTP authentication |
gitea admin auth update-smtp |
Update SMTP authentication |
gitea admin auth delete |
Delete authentication source |
gitea admin repo-sync-releases |
Sync releases with Git tags |
gitea admin regenerate hooks |
Regenerate Git hooks |
gitea admin regenerate keys |
Regenerate SSH keys |
gitea admin sendmail |
Send email to all users |
gitea migrate |
Run database migrations |
gitea doctor check |
Run health checks |
gitea doctor check --fix |
Run health checks and fix issues |
gitea manager |
Runtime management (flush queues, shutdown, etc.) |
gitea dump |
Create backup archive |
gitea dump-repository |
Dump repository data |
gitea restore-repository |
Restore repository from dump |
gitea embedded list |
List embedded resources |
gitea embedded extract |
Extract embedded resources |
gitea migrate-storage |
Migrate between storage backends |
gitea actions generate-runner-token |
Generate runner registration token |
gitea cert |
Generate self-signed TLS certificate |
gitea generate secret |
Generate secrets (INTERNAL_TOKEN, JWT_SECRET, etc.) |
gitea config |
Display resolved configuration |
gitea docs |
Generate CLI documentation |
Tip
Add
--helpto any command for usage details and available flags. Thegitea configcommand is standalone and does not require a running instance or database connection.
Backup and Restore
Create a full backup:
gitea dump --type zip --file /backup/gitea-$(date +%Y%m%d).zip
The dump includes: database, repositories, configuration, custom files, log, and data directories.
Restore procedure:
- Stop Gitea
- Extract the dump archive
- Restore the database from the SQL dump
- Copy repositories and data directories to the correct paths
- Restore
app.iniconfiguration - Start Gitea and run
gitea migrateif upgrading
Note
gitea dump-repositoryandgitea restore-repositoryare separate commands for individual repository export/import, useful for migration between instances.
Monitoring
Gitea exposes Prometheus metrics at /metrics when enabled:
[metrics]
ENABLED = true
TOKEN = your-metrics-token
Key metrics exposed:
gitea_accesses— number of access operationsgitea_actions— CI/CD action countsgitea_issues— issue counts by stategitea_repositories— repository countsgitea_users— user counts by type
A Grafana monitoring mixin is provided in contrib/gitea-monitoring-mixin/ with pre-built dashboards.
Tip
Use the health endpoint at
/api/v1/healthfor basic liveness checks. It does not require authentication.
Database Maintenance
Run pending migrations:
gitea migrate
Supported database operations:
- Automatic schema migration on version upgrades
- XORM handles cross-database SQL generation
- Per-database atomic resource indexing (PostgreSQL uses
ON CONFLICT ... RETURNING, MySQL usesON DUPLICATE KEY UPDATE, MSSQL usesMERGE)
Queue System
Gitea uses an internal queue system for asynchronous processing (webhooks, indexing, notifications, etc.). Queue backends:
| Backend | Configuration | Best For |
|---|---|---|
channel |
In-memory Go channels | Development, small instances |
level |
LevelDB on disk | Single-instance production |
redis |
Redis connection | High-availability setups |
Cron Jobs
Gitea includes built-in cron jobs managed via the [cron] configuration section:
- Repository health checks
- Mirror synchronization
- Archive cleanup
- Statistics generation
- Indexer flushing
These are managed by the gocron scheduler and can be configured or disabled via app.ini.
6. Performance
Search Indexer Options
| Indexer | Config | Performance Profile |
|---|---|---|
| bleve (default) | [indexer] ISSUE_INDEXER_TYPE = bleve |
Good for small/medium instances |
| Elasticsearch | [indexer] ISSUE_INDEXER_TYPE = elasticsearch |
Better for large instances (100K+ issues) |
| Meilisearch | [indexer] ISSUE_INDEXER_TYPE = meilisearch |
Fast, lightweight alternative |
| None | [indexer] REPO_INDEXER_ENABLED = false |
Disable code search to save resources |
Cache Configuration
- Memory cache (default): Zero configuration, lost on restart
- Redis: Shared cache, persistent, recommended for production
- Memcache: Alternative shared cache
- TwoQueue: Memory-based with eviction policies
[cache]
ADAPTER = redis
HOST = redis://127.0.0.1:6379/0
ITEM_TTL = 16h
Git Performance
- Gitea uses the commit-graph feature for faster Git operations when available
- The
[git]section allows tuning Git operation timeouts - Large repository support can be improved by enabling commit caching
7. Security
Authentication Methods
| Method | Type | Configuration |
|---|---|---|
| Local | Built-in | Default, no additional config |
| LDAP (Bind DN) | External | gitea admin auth add-ldap |
| LDAP (Simple Auth) | External | gitea admin auth add-ldap-simple |
| OAuth2 | External | gitea admin auth add-oauth (GitHub, GitLab, Google, etc.) |
| SMTP | External | gitea admin auth add-smtp |
| PAM | External | Build with pam tag |
| SSPI | External | Windows integrated auth (build with sspi tag, Windows only) |
| WebAuthn/FIDO2 | 2FA | User-enabled via Settings > Security |
| TOTP | 2FA | User-enabled via Settings > Security |
Warning
LDAP bind passwords are stored in the database. Use a dedicated service account with minimal privileges for LDAP authentication. The
[admin]section'sUSER_DISABLED_FEATUREScan restrict what external auth users can modify.
OAuth2 Provider
Gitea can act as an OAuth2 authorization server, allowing third-party applications to authenticate against your Gitea instance. Configure OAuth2 applications in Site Administration > Applications.
CAPTCHA Support
Gitea supports multiple CAPTCHA providers to prevent automated registration:
| Provider | Configuration Key |
|---|---|
| reCAPTCHA | [service] ENABLE_CAPTCHA = true, [recaptcha] section |
| hCaptcha | [service] ENABLE_CAPTCHA = true, [hcaptcha] section |
| mCaptcha | [service] ENABLE_CAPTCHA = true, [mcaptcha] section |
| Cloudflare Turnstile | [service] ENABLE_CAPTCHA = true, [turnstile] section |
Secret Management
| Secret | Purpose | Generation |
|---|---|---|
INTERNAL_TOKEN |
Internal API authentication | gitea generate secret INTERNAL_TOKEN |
JWT_SECRET |
JWT token signing | gitea generate secret JWT_SECRET |
LFS_JWT_SECRET |
LFS JWT signing | gitea generate secret LFS_JWT_SECRET |
SECRET_KEY |
Global secret key | gitea generate secret SECRET_KEY |
Caution
Never expose
INTERNAL_TOKENor database credentials. The internal API (routers/private/) is used for SSH-to-web communication and has elevated privileges. It should never be exposed to external networks.
Signing Support
Gitea supports GPG and SSH signing for commits and tags:
- Sign commits using GPG keys managed in User Settings > SSH / GPG Keys
- Gitea can verify commit signatures and display verification status
- SSH keys from LDAP can be automatically flagged as verified via
--ssh-keys-are-verified
Host Access Control
The modules/hostmatcher package provides URL/host allowlisting and denylisting for outgoing requests (webhooks, migrations, etc.) to prevent SSRF attacks.
8. Developer Internals
Repository Structure
gitea/
├── main.go # Entry point, version info, CLI dispatch
├── cmd/ # CLI commands (52 files, 282K chars)
│ ├── main.go # Command tree: web, admin, doctor, dump, etc.
│ ├── web.go # Web server startup
│ ├── admin.go # Admin subcommands
│ ├── doctor.go # Health check system
│ └── ...
├── models/ # Data layer (XORM models)
│ ├── db/ # Database engine, migrations infrastructure
│ ├── user/ # User model (28 files)
│ ├── repo/ # Repository model (47 files)
│ ├── issues/ # Issue/PR model (47 files, 460K chars — largest)
│ ├── actions/ # CI/CD action models
│ ├── auth/ # Authentication source models
│ ├── packages/ # Package registry models
│ ├── git/ # Git-specific models (branches, commits, LFS)
│ ├── organization/ # Organization model
│ ├── project/ # Project board model
│ ├── webhook/ # Webhook models
│ └── migrations/ # Database schema migrations
├── modules/ # Shared libraries
│ ├── setting/ # Configuration system (75 files, 294K chars)
│ ├── git/ # Git operations (113 files, 333K chars)
│ ├── markup/ # Markdown/Org rendering
│ ├── storage/ # Storage abstraction (local, S3, Azure)
│ ├── queue/ # Async queue system
│ ├── indexer/ # Search indexer (bleve, ES, Meilisearch)
│ ├── cache/ # Caching layer
│ ├── log/ # Logging system
│ ├── structs/ # API request/response structs (55 files)
│ ├── graceful/ # Graceful shutdown manager
│ ├── actions/ # Actions workflow support
│ ├── packages/ # Package format parsers
│ └── ...
├── routers/ # HTTP routing
│ ├── web/ # Web UI routes (go-chi)
│ ├── api/ # REST API + package registry
│ ├── private/ # Internal API (SSH ↔ web)
│ └── install/ # Installation wizard
├── services/ # Business logic
│ ├── pull/ # Pull request operations (31 files, 220K chars)
│ ├── repository/ # Repository management (38 files, 245K chars)
│ ├── migrations/ # Migration from other forges (26 files, 273K chars)
│ ├── actions/ # CI/CD service layer (24 files, 163K chars)
│ ├── webhook/ # Webhook delivery (26 files, 263K chars)
│ ├── context/ # Request context (27 files, 124K chars)
│ ├── issue/ # Issue operations
│ ├── auth/ # Authentication service
│ ├── mailer/ # Email delivery
│ ├── forms/ # Form validation
│ └── ...
├── templates/ # Go HTML templates
├── web_src/ # Frontend source
│ ├── js/ # JavaScript/TypeScript
│ ├── css/ # Stylesheets
│ └── fomantic/ # Fomantic UI customization
├── options/ # Embedded resources
│ ├── locale/ # 28 locale files (5.2M chars)
│ ├── license/ # License templates
│ ├── gitignore/ # .gitignore templates
│ └── label/ # Issue label presets
├── contrib/ # Service manager configs, IDE configs
├── docker/ # Dockerfile and rootless variant
├── snap/ # Snap package definition
└── tests/ # Integration, e2e, fuzz tests
Architecture Deep Dive
Request lifecycle:
- HTTP request arrives at
go-chirouter (routers/web/web.goorrouters/api/) - Middleware chain: logging, context setup, auth, CSRF, session
- Handler calls service layer (
services/) - Service calls model layer (
models/) for data access - Models use XORM to execute database queries
- Response rendered via Go templates (
templates/) or JSON
Key design patterns:
- Service context:
services/contextprovides a rich request context wrapping chi's context with user info, repository info, permissions, and flash messages - Notification system:
services/notifyimplements a fan-out notification pattern — actions trigger notifications that are dispatched to multiple handlers (email, webhook, UI, indexer) - Storage abstraction:
modules/storageprovides a unified interface for file storage, with implementations for local filesystem, MinIO/S3, and Azure Blob Storage - Queue system:
modules/queueimplements async job processing with multiple backends (channel, level, redis)
Module Reference
modules/setting (75 files, 294K chars)
The configuration system. Each config section has its own file (e.g., actions.go, database.go, server.go). Settings are loaded from INI files via gopkg.in/ini.v1, with environment variable overrides applied via the GITEA__ prefix mechanism in config_env.go.
modules/git (113 files, 333K chars)
Git operations wrapper. Provides Go interfaces for Git commands, including repository operations, commit parsing, diff generation, and LFS support. Uses native Git CLI commands via os/exec with structured error handling.
models/issues (47 files, 460K chars)
The largest model package. Covers issues, pull requests, comments, labels, milestones, reactions, time tracking, and issue dependencies. Uses XORM for database access with per-database optimized queries.
services/pull (31 files, 220K chars)
Pull request business logic: creation, merging (merge, rebase, squash, fast-forward, manually merged), auto-merge scheduling, update branch, code review, and conflict detection.
services/webhook (26 files, 263K chars)
Webhook delivery system supporting multiple services (Gitea, Gogs, Slack, Discord, Telegram, Matrix, Microsoft Teams, Feishu, DingTalk, WeCom, Packagist). Each service has its own payload formatter.
services/migrations (26 files, 273K chars)
Repository migration from external platforms. Each platform has a downloader implementation: GitHub, GitLab, Gitea, Gogs, Bitbucket, OneDev, CodeCommit, Codebase.
services/actions (24 files, 163K chars)
Gitea Actions CI/CD backend. Handles workflow parsing, job queuing, runner communication (via gRPC/connect-rpc), artifact storage, and log management.
routers/api (REST API)
The Swagger-documented REST API provides programmatic access to all Gitea features. API routes are defined with go-swagger annotations and compiled into templates/swagger/v1_json.tmpl. Key API areas:
/api/v1/repos/— Repository CRUD and management/api/v1/user/— User operations/api/v1/orgs/— Organization management/api/v1/admin/— Admin operations/api/v1/packages/— Package registry API/api/v1/settings/— Instance settings
Code Patterns and Conventions
Naming:
- Copyright headers:
// Copyright YYYY The Gitea Authors. All rights reserved.with SPDX license identifier - Go packages follow standard naming (lowercase, no underscores)
- Test files use
_test.gosuffix
Error handling:
- Structured error types in
modules/git/error.goand domain-specific error types - Errors wrap with
fmt.Errorf("context: %w", err)for stack context - The
runStdErrortype captures Git command stderr for debugging
Database:
- XORM ORM with per-database optimized queries (PostgreSQL, MySQL, SQLite, MSSQL)
- Atomic resource index via database-specific upsert patterns
- Schema migrations in
models/migrations/with version tracking - WAL mode for SQLite, FK enforcement
Linting:
golangci-lintwith strict configuration (.golangci.yml)- Custom rules: no
encoding/json(usemodules/json), nogopkg.in/ini.v1(use config system), noio/ioutil - Go header check enforcing copyright format
eslintfor TypeScript/JavaScript with comprehensive rule set
Build system:
- Makefile-driven (
make build,make test-sqlite,make test-pgsql,make test-mysql,make test-mssql) - Build tags:
bindata(embed assets),sqlite(SQLite support),pam(PAM auth) - Frontend: Vite bundler, TypeScript, ESLint
airfor hot-reload development
Testing Strategy
Test types:
| Type | Location | Command |
|---|---|---|
| Unit tests | *_test.go alongside source |
go test ./... |
| Integration tests | tests/integration/ |
make test-sqlite, make test-pgsql, make test-mysql, make test-mssql |
| E2E tests | tests/e2e/ |
Playwright-based browser tests |
| Fuzz tests | tests/fuzz/ |
Go fuzz testing |
Integration test databases:
# SQLite (simplest)
make test-sqlite
# PostgreSQL (via Docker)
docker run -e "POSTGRES_DB=test" -e "POSTGRES_USER=postgres" -e "POSTGRES_PASSWORD=postgres" -p 5432:5432 --rm postgres:latest
TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=postgres TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql
# MySQL (via Docker)
docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm mysql:latest
TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root make test-mysql
# MSSQL (via Docker)
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm microsoft/mssql-server-linux:latest
TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql
Running individual tests:
make test-sqlite#GPG
Test environment variables:
| Variable | Purpose |
|---|---|
GITEA_TEST_SLOW_RUN |
Threshold for slow test warning (default: 10s) |
GITEA_TEST_SLOW_FLUSH |
Threshold for slow flush warning (default: 1s) |
GITEA_TEST_LOG_SQL |
Enable SQL query logging in tests |
GITEA_TEST_E2E |
Enable E2E test mode |
Running tests via local act_runner:
# All jobs (resource-intensive, not recommended)
act_runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest
# Single job
act_runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -j <job_name>
# List all jobs
act_runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -l
Doctor System
The gitea doctor command (services/doctor/) provides automated health checking and repair:
- Database consistency checks
- Repository integrity verification
- SSH authorized_keys file synchronization
- Git hook file verification
- Permission checks
Potential Improvements
models/issues(460K chars, 47 files) — the largest model package — could benefit from decomposition- The REST API is defined via Swagger annotations spread across many files; centralized API versioning is implicit
- Frontend uses a mix of vanilla JS, jQuery-era patterns (Fomantic UI), and modern Vue.js — a gradual migration toward Vue components is ongoing
- Configuration (75 files) covers many sections but lacks a formal schema validation layer
Contributing
Quick setup:
git clone https://github.com/go-gitea/gitea.git
cd gitea
make deps-frontend deps-backend
TAGS="bindata sqlite sqlite_unlock_notify" make build
./gitea web
Development hot-reload:
# Uses air (.air.toml) for automatic rebuild
air
Code style: Enforced by golangci-lint and eslint. Run make lint-backend lint-frontend before committing.
PR labels used for changelog:
| Label | Category |
|---|---|
pr/breaking |
BREAKING |
topic/security |
SECURITY |
type/feature |
FEATURES |
type/enhancement |
ENHANCEMENTS |
type/bug |
BUGFIXES |
modifies/api |
API |
type/testing |
TESTING |
type/docs |
DOCS |
Translations are managed via Crowdin — do not edit locale files directly.
9. Integration Points
REST API
Full Swagger documentation at {ROOT_URL}/api/swagger. The API supports:
- Token-based authentication (personal access tokens, OAuth2 tokens)
- Pagination via
pageandlimitquery parameters - Default page size: 30, max items: 50 (configurable)
Webhook Events
Gitea fires webhooks for repository events:
create,delete,fork,push,issues,issue_assign,issue_label,issue_milestone,issue_commentpull_request,pull_request_assign,pull_request_label,pull_request_milestone,pull_request_comment,pull_request_review,pull_request_syncrepository,release,package,wiki,pull_request_review_request
Each webhook delivery includes: event type, delivery ID, payload JSON, sender information, and signature (HMAC-SHA256).
Package Registry
Gitea's package registry implements standard protocols for 20+ package formats. Each format lives in its own module:
- Models:
models/packages/+models/packages/<type>/ - Parser:
modules/packages/<type>/ - Router:
routers/api/packages/<type>/ - Service:
services/packages/+services/packages/<type>/
Go Modules (go-get)
Gitea serves go-get meta tags for Go module discovery. When a ?go-get=1 query parameter is present, Gitea returns HTML with <meta name="go-import"> and <meta name="go-source"> tags pointing to the repository.
SSH Integration
Gitea supports two SSH modes:
- Built-in SSH server (
[server] START_SSH_SERVER = true): Usesgithub.com/gliderlabs/ssh - OpenSSH integration: Gitea manages the
authorized_keysfile and uses thegitea servcommand as the shell for Git operations
The cmd/hook.go processes Git hooks (pre-receive, update, post-receive) to enforce branch protections, trigger webhooks, and update indexes.
Migration Protocols
| Platform | Downloader | Features |
|---|---|---|
| GitHub | services/migrations/github.go |
Repos, issues, PRs, labels, milestones, releases, wiki, comments |
| GitLab | services/migrations/gitlab.go |
Repos, issues, PRs, labels, milestones, releases, wiki, comments |
| Gitea | services/migrations/gitea_downloader.go |
Full bidirectional migration |
| Gogs | services/migrations/gogs.go |
Repos, issues, labels, milestones |
| Bitbucket | services/migrations/bitbucket.go |
Repos, issues, PRs |
| OneDev | services/migrations/onedev.go |
Repos, issues, PRs |
| CodeCommit | services/migrations/codecommit.go |
Repos |
| Codebase | services/migrations/codebase.go |
Repos, issues |
Prometheus Metrics
Available at /metrics when [metrics] ENABLED = true. Provides standard Go runtime metrics plus Gitea-specific counters. A Grafana mixin (contrib/gitea-monitoring-mixin/) provides ready-made dashboards.
IDE Integration
Gitea supports "Open With" external editors. Default configured editors:
| Editor | URL Scheme |
|---|---|
| VS Code | vscode://vscode.git/clone?url={url} |
| VSCodium | vscodium://vscode.git/clone?url={url} |
| IntelliJ IDEA | jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo={url} |
Custom editors can be configured in [repository.editor] or via the admin panel.
VS Code workspace settings are provided in contrib/ide/vscode/ for contributors.
10. Troubleshooting
Error: Stuck database migrations
Cause: Migration was interrupted during a schema change.
Solution:
- Check
versiontable in the database for current migration version - Manually investigate the failed migration in
models/migrations/ - Apply the migration manually if needed, then update the version
Warning
Never manually edit migration version numbers without understanding the schema change. Always back up the database before attempting manual migration fixes.
Error: SSH operations fail with permission denied
Cause: SSH key not properly registered, or authorized_keys file is out of sync.
Solution:
- Run
gitea admin regenerate keysto rebuild the authorized_keys file - Verify the
gituser's home directory has correct permissions - Check that
gitea servis correctly configured as the SSH shell
Error: High memory usage
Cause: Large repository operations, indexer, or unbounded caching.
Solution:
- Monitor queue depths via
gitea manageror Prometheus metrics - Tune
[cache] ITEM_TTLand consider using Redis - Reduce
[repository] MAX_CREATION_LIMITif needed - Consider disabling repository code indexing for very large instances
Tip
The
gitea managercommand provides runtime management capabilities including flushing queues, dumping goroutine stacks, and graceful shutdown — useful for diagnosing production issues without restarting.
Error: Webhook delivery timeouts
Cause: Target service is slow or unreachable.
Solution:
- Check webhook delivery logs in the admin panel
- Increase timeout settings if the target is legitimately slow
- Verify network connectivity from the Gitea server
- Check for TLS issues if using HTTPS webhook targets
Error: Flaky LDAP authentication
Cause: Network instability, LDAP server load, or configuration issues (go-gitea/gitea#37263).
Solution:
- Verify LDAP server is responsive:
ldapsearch -H ldap://server -x -b "dc=example,dc=com" - Check the security protocol setting matches the server configuration
- Verify the bind DN has read access to the user search base
- Enable debug logging for auth operations