Gitea — Architecture#
Architectural style#
Layered monolith — a single deployable binary with a strict four-tier horizontal layering enforced by convention rather than the Go toolchain. The layers are:
modules/— infrastructure and cross-cutting utilities (no imports from upper layers)models/— data access layer (xorm ORM; importsmodules/, notservices/orrouters/)services/— business logic orchestration (importsmodels/andmodules/)routers/— HTTP handler layer (importsservices/; does not importmodels/directly)
This downward-only DAG is the defining architectural constraint. The project also incorporates several async subsystems (queue-based background workers, cron jobs, event source notifications) that operate as background goroutines within the same process.
Component diagram (textual)#
┌──────────────────────────────────────────────────────────────────────────────┐
│ gitea binary │
│ │
│ ┌─────────────────────────────── routers/ ──────────────────────────────┐ │
│ │ web/ api/v1/ api/packages/ api/actions/ private/ │ │
│ │ (HTML UI) (REST API) (pkg registry) (runner API) (hook IPC) │ │
│ └──────────────────┬──────────────────────────────────────────┬─────────┘ │
│ │ calls │ │
│ ┌──────────────────▼──────────────────────────────────────────▼─────────┐ │
│ │ services/ │ │
│ │ context/ auth/ repository/ pull/ issue/ actions/ packages/ │ │
│ │ mailer/ webhook/ cron/ migrations/ indexer/ convert/ ... │ │
│ └──────────────────┬───────────────────────────────────────────────────┘ │
│ │ calls │
│ ┌──────────────────▼──────────────────────────────────────────────────┐ │
│ │ models/ │ │
│ │ repo/ user/ issues/ pull/ auth/ org/ actions/ │ │
│ │ packages/ webhook/ git/ perm/ db/ migrations/ │ │
│ └──────────────────┬───────────────────────────────────────────────────┘ │
│ │ uses │
│ ┌──────────────────▼──────────────────────────────────────────────────┐ │
│ │ modules/ │ │
│ │ setting log git queue cache storage graceful indexer │ │
│ │ markup ssh lfs web process session templates public ... │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────┐ ┌──────────────────────────────────────────────────┐ │
│ │ SSH Server │ │ Background Goroutines │ │
│ │ (modules/ssh) │ │ mirrors · webhooks · mailer · indexer │ │
│ │ git-over-SSH │ │ automerge · archiver · cron · actions │ │
│ └────────────────┘ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
External:
git subprocess ──(private HTTP IPC)──▶ routers/private/
Gitea Actions runner ────────────────▶ routers/api/actions/
Package managers ───────────────────▶ routers/api/packages/Core components#
modules/setting#
- Package:
code.gitea.io/gitea/modules/setting - Responsibility: Load and expose all application configuration. Reads
app.ini(an INI file viago-ini) with environment variable overrides. Exposes a large set of package-level variables (setting.AppURL,setting.Database,setting.SSH, etc.) consumed globally. Also providesCfgProviderfor programmatic access. - Key types: Package-level vars;
ConfigProviderinterface;LoadSettings()/LoadSettingsForInstall() - Dependencies:
modules/log,go-ini/ini
modules/graceful#
- Package:
code.gitea.io/gitea/modules/graceful - Responsibility: Coordinate graceful shutdown across all server components. Tracks a count of active servers (
numberOfServersToCreate = 4), providesHammerContext()for hard cancellation, andRunAtShutdown()for cleanup callbacks. Uses OS signal handling (SIGTERM, SIGUSR1 for hot-reload via socket inheritance). - Key types:
Manager,RunCancelerinterface - Dependencies:
modules/process,modules/log
modules/queue#
- Package:
code.gitea.io/gitea/modules/queue - Responsibility: Generic, type-parametrized async job queue.
WorkerPoolQueue[T]manages a pool of goroutines processing batches of items. Supports three backends: in-memory channel, LevelDB-backed queue (base_levelqueue), or Redis. All async operations in services (webhook delivery, indexer updates, task scheduling) go through this queue. - Key types:
WorkerPoolQueue[T any],HandlerFuncT[T],ManagedWorkerPoolQueue,Manager - Dependencies:
modules/setting,modules/log,modules/process
services/context#
- Package:
code.gitea.io/gitea/services/context - Responsibility: Per-request context carrier.
Contextstruct holds the current*user_model.User(Doer),*Repository,*Organization,*Package, session, flash, render helper, and template data. This is the primary integration point between the HTTP handler layer and domain logic. Stored in the chi request context by middleware; retrieved by handlers viactx.GetWebContext(req). - Key types:
Context,Base,Repository,Organization,APIContext - Dependencies:
models/user,modules/session,modules/cache,modules/templates,modules/web
models/db#
- Package:
code.gitea.io/gitea/models/db - Responsibility: xorm engine lifecycle, transaction helpers, generic
Find/Count/Exist/Insert/Updateutilities, and theListOptions/FindPageAndCountpaging primitives used throughoutmodels/. Owns the global xorm engine singleton. - Key types:
Engine,Context(ctx with engine embedded),ListOptions,DefaultContext - Dependencies:
xorm.io/xorm,modules/setting,modules/log
routers (init.go + NormalRoutes)#
- Package:
code.gitea.io/gitea/routers - Responsibility: Top-level bootstrap and route mount point.
InitWebInstalled()is the ordered initialization sequence for all services.NormalRoutes()composes the chi root router by mounting sub-routers at canonical prefixes (/,/api/v1,/api/internal,/api/packages,/v2,/api/actions). - Key types:
*web.Router(wraps chi.Router) - Dependencies: All service packages, all router packages,
modules/web
routers/private#
- Package:
code.gitea.io/gitea/routers/private - Responsibility: Internal HTTP IPC between the git hook subprocess (
gitea hook) and the running web server. Routes like/api/internal/hook/pre-receive,/api/internal/hook/post-receive, and/api/internal/repo/...are only bound to a Unix socket or localhost. This avoids filesystem IPC and keeps hook→server communication typed and authenticated. - Key types:
Routes()function; handler functions for hook events - Dependencies:
services/repository,services/git
Data flow#
HTTP web request (e.g., viewing a repository)#
Browser HTTP GET /owner/repo
→ modules/web.Router (chi)
→ common.ProtocolMiddlewares() (rate limit, proxy headers, CORS)
→ web_routers.Routes() mount at "/"
→ auth middleware (services/auth pipeline):
session → basic auth → token → OAuth2 → SSPI
sets services/context.Context.Doer
→ repo middleware (services/context repo.go):
loads models/repo.Repository, models/perm permissions
sets context.Repo
→ handler func (e.g., routers/web/repo/code.go):
calls services/repository.* or models/repo.*
populates context.Data (template vars)
calls ctx.HTML(200, "repo/home")
→ services/context.Context.Render.HTML()
→ modules/templates.PageRenderer
→ html/template execution
← HTTP response (rendered HTML)Async operation (e.g., webhook delivery after push)#
git push over SSH
→ modules/ssh SSH server
→ gitea serv subprocess (cmd/serv.go)
→ git-receive-pack subprocess
→ gitea hook post-receive (cmd/hook.go)
→ POST /api/internal/hook/post-receive (routers/private)
→ services/repository.PushUpdates()
→ queue.Push(webhook.HookTask) ← enqueued
→ WorkerPoolQueue goroutine pops task
→ services/webhook.Deliver()
→ HTTP POST to webhook URLREST API request (e.g., creating an issue)#
POST /api/v1/repos/owner/repo/issues
→ routers/api/v1.Routes() (chi)
→ auth middleware: token / OAuth2 / basic
→ APIContext (services/context.APIContext)
→ routers/api/v1/repo/issue.go CreateIssue()
→ services/issue.NewIssue()
→ models/issues.CreateIssue() (xorm insert)
→ services/mailer.SendIssueCommentMail() → queue
→ services/webhook.PrepareWebhooks() → queue
→ services/indexer.UpdateRepoIndexer() → queue
← JSON responseInitialization / Bootstrap#
The startup sequence in routers.InitWebInstalled() (called from cmd/web.go serveInstalled) is strictly ordered:
- git.InitFull — verify git binary, detect git version and feature flags
- translation.InitLocales — load i18n locale files (from bindata or disk)
- setting.LoadSettings — parse
app.ini, populate all setting vars - storage.Init — initialize pluggable file storage backends (local/S3/MinIO)
- mailer.NewContext — set up SMTP/sendmail mailer
- cache.Init — connect to Redis/in-memory cache
- feed_service.Init, uinotification.Init, archiver.Init — minor services
- markup / external renderers — register Markdown, orgmode, asciidoc renderers
- common.InitDBEngine — open xorm DB connection, run pending migrations
- system.Init — load runtime state (AppPath tracking)
- oauth2.Init, oauth2_provider.Init — OAuth2 client + OIDC provider
- release_service.Init — release-related initialization
- models.Init, authmodel.Init, repo_service.Init — domain model init
- indexer_service.Init — start background indexer (Bleve/ES/Meilisearch)
- Background goroutines: mirror sync, webhook dispatcher, pull-request automerge, task runner, migration runner
- eventsource.GetManager().Init — SSE (Server-Sent Events) for live UI updates
- mailer_incoming.Init — incoming mail polling (if configured)
- syncAppConfForGit — re-sync git hooks if AppPath changed
- ssh.Init — start built-in SSH server (if enabled)
- auth.Init — register auth source providers (LDAP, SAML, PAM, SSPI)
- svg.Init — load SVG icon cache
- actions_service.Init — initialize Gitea Actions runner bridge
- repo_service.InitLicenseClassifier — load license detection data
- cron.Init — start all periodic cron tasks (mirrors, GC, cleanup)
After this sequence, routers.NormalRoutes() builds the chi router tree and cmd/web.go listen() binds it to the configured protocol (HTTP/HTTPS/FCGI/Unix socket).
No dependency injection framework is used. All wiring is manual, via package-level init() functions and explicit calls to mustInit(fn) / mustInitCtx(ctx, fn). The mustInit wrapper uses reflect+runtime.FuncForPC to print the function name on failure before calling log.Fatal.
Configuration#
- Format: INI file (
app.ini) located atCustomPath/conf/app.ini, parsed bygo-ini/iniviamodules/setting. - Custom path: Gitea respects
GITEA_CUSTOMenv,--custom-pathflag, or a compiled-in default. Everything underCustomPath/(templates, locales, public files) overrides the embedded defaults. - Environment overrides: Any
app.inikey can be overridden withGITEA__SECTION__KEY=valueenvironment variables. - Install wizard: On first run (
InstallLock = false),serveInstall()runs a limited HTTP server serving only the install page (routers/install). On form submit, it writesapp.iniand restarts. - Runtime reload:
gitea manager reload-templatesandgitea manager flush-queuesinteract with the running server viarouters/privateIPC. Full configuration reload is done via a graceful restart (SIGUSR1 + socket inheritance). - Feature flags: Build tags (
sqlite,bindata,timetzdata) enable/disable features at compile time.
Key design decisions#
Strict layer DAG without
internal/. The four-layer architecture (modules→models→services→routers) is Gitea’s core structural discipline. Unlike most large Go projects, there is nointernal/boundary — all packages are importable by any Go code. Layering discipline is enforced by convention and CI linting. This is a pragmatic trade-off: it lowers the barrier for contributors and allows test packages to reach any layer, at the cost of relying on team discipline for import direction.Single binary, multiple roles, urfave/cli dispatch. The same
giteabinary handles HTTP serving, SSH git service, hook callbacks, admin operations, and health checks. Subcommand dispatch viaurfave/cliv3 (not cobra) routes execution. Thegitea servandgitea hooksubcommands are invoked by SSHauthorized_keysand git hook scripts respectively, then communicate back to the running web process via the private HTTP IPC channel.WorkerPoolQueue[T]for all async operations. Webhook delivery, search indexing, email sending, automerge scheduling, and Actions task dispatch all use the same genericWorkerPoolQueue[T any]type. The backend is swappable at configuration time (in-memory channel, LevelDB, Redis) without changing call sites. This provides persistence and flow control while keeping service code simple.services/context.Contextas the HTTP boundary object. Rather than passing individual domain objects to handlers, Gitea loads a richContextstruct (user, repo, org, permissions, flash, session, cache) in middleware and passes it to every handler. This is the central seam between the routing and service layers. It avoids long parameter lists but makes the context struct itself a heavy object that must be carefully initialized.Private HTTP IPC instead of filesystem or pipe IPC. Git hook processes (
pre-receive,post-receive,update) communicate with the running server over a Unix socket HTTP API (routers/private). This avoids the fragility of parsing shell output or using named pipes, gives the server full type-safe access to hook events, and allows the server to enforce authorization and quota checks synchronously during the push.