# Gitea Administrator & Developer Guide

## 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

```text
                        ┌──────────────────────────────────────────┐
                        │              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

```text
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 `git` binary. 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:

```bash
# [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):

```bash
GITEA__database__PASSWD__FILE=/run/secrets/db_password
```

> [!IMPORTANT]
> Environment variables take precedence over `app.ini` settings. 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 like `uses: actions/checkout@v4` resolve 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:
> ```ini
> [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):**

```yaml
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:2222` for SSH, `-p 3000:3000` for HTTP).

### Systemd Service

```ini
[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

```ini
[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:**

```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_URL` to the external URL (e.g., `https://git.example.com/`) and ensure the proxy forwards the `Host`, `X-Real-IP`, and `X-Forwarded-Proto` headers.

### Snap Deployment

```bash
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 `--help` to any command for usage details and available flags. The `gitea config` command is standalone and does not require a running instance or database connection.

### Backup and Restore

**Create a full backup:**

```bash
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:**

1. Stop Gitea
2. Extract the dump archive
3. Restore the database from the SQL dump
4. Copy repositories and data directories to the correct paths
5. Restore `app.ini` configuration
6. Start Gitea and run `gitea migrate` if upgrading

> [!NOTE]
> `gitea dump-repository` and `gitea restore-repository` are separate commands for individual repository export/import, useful for migration between instances.

### Monitoring

Gitea exposes Prometheus metrics at `/metrics` when enabled:

```ini
[metrics]
ENABLED = true
TOKEN   = your-metrics-token
```

**Key metrics exposed:**

- `gitea_accesses` — number of access operations
- `gitea_actions` — CI/CD action counts
- `gitea_issues` — issue counts by state
- `gitea_repositories` — repository counts
- `gitea_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/health` for basic liveness checks. It does not require authentication.

### Database Maintenance

**Run pending migrations:**

```bash
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 uses `ON DUPLICATE KEY UPDATE`, MSSQL uses `MERGE`)

### 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

```ini
[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's `USER_DISABLED_FEATURES` can 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_TOKEN` or 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

```text
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:**

1. HTTP request arrives at `go-chi` router (`routers/web/web.go` or `routers/api/`)
2. Middleware chain: logging, context setup, auth, CSRF, session
3. Handler calls service layer (`services/`)
4. Service calls model layer (`models/`) for data access
5. Models use XORM to execute database queries
6. Response rendered via Go templates (`templates/`) or JSON

**Key design patterns:**

- **Service context:** `services/context` provides a rich request context wrapping chi's context with user info, repository info, permissions, and flash messages
- **Notification system:** `services/notify` implements a fan-out notification pattern — actions trigger notifications that are dispatched to multiple handlers (email, webhook, UI, indexer)
- **Storage abstraction:** `modules/storage` provides a unified interface for file storage, with implementations for local filesystem, MinIO/S3, and Azure Blob Storage
- **Queue system:** `modules/queue` implements 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.go` suffix

**Error handling:**
- Structured error types in `modules/git/error.go` and domain-specific error types
- Errors wrap with `fmt.Errorf("context: %w", err)` for stack context
- The `runStdError` type 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-lint` with strict configuration (`.golangci.yml`)
- Custom rules: no `encoding/json` (use `modules/json`), no `gopkg.in/ini.v1` (use config system), no `io/ioutil`
- Go header check enforcing copyright format
- `eslint` for 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
- `air` for 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:**

```bash
# 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:**

```bash
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:**

```bash
# 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:**

```bash
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:**

```bash
# 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](https://crowdin.com/project/gitea) — 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 `page` and `limit` query 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_comment`
- `pull_request`, `pull_request_assign`, `pull_request_label`, `pull_request_milestone`, `pull_request_comment`, `pull_request_review`, `pull_request_sync`
- `repository`, `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:

1. **Built-in SSH server** (`[server] START_SSH_SERVER = true`): Uses `github.com/gliderlabs/ssh`
2. **OpenSSH integration**: Gitea manages the `authorized_keys` file and uses the `gitea serv` command 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:**
1. Check `version` table in the database for current migration version
2. Manually investigate the failed migration in `models/migrations/`
3. 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:**
1. Run `gitea admin regenerate keys` to rebuild the authorized_keys file
2. Verify the `git` user's home directory has correct permissions
3. Check that `gitea serv` is correctly configured as the SSH shell

### Error: High memory usage

**Cause:** Large repository operations, indexer, or unbounded caching.

**Solution:**
1. Monitor queue depths via `gitea manager` or Prometheus metrics
2. Tune `[cache] ITEM_TTL` and consider using Redis
3. Reduce `[repository] MAX_CREATION_LIMIT` if needed
4. Consider disabling repository code indexing for very large instances

> [!TIP]
> The `gitea manager` command 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:**
1. Check webhook delivery logs in the admin panel
2. Increase timeout settings if the target is legitimately slow
3. Verify network connectivity from the Gitea server
4. 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:**
1. Verify LDAP server is responsive: `ldapsearch -H ldap://server -x -b "dc=example,dc=com"`
2. Check the security protocol setting matches the server configuration
3. Verify the bind DN has read access to the user search base
4. Enable debug logging for auth operations

---

_Generated May 2026 from go-gitea/gitea at 19f0169. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
