Files
2026-09-03 14:26:31 +02:00

130 lines
6.1 KiB
Markdown

# Gitea
Self-hosted Git with a web UI, issues and pull requests. A few hundred MB of Go
that starts in a second, where the alternative is a JVM and a shopping list of
services.
This is the stack the rest of this repo is developed against — the compose files
you're reading live in a Gitea instance, and GitHub is the mirror.
## Quick start
```bash
cp .env.example .env
$EDITOR .env # PUID/PGID must match the owner of ./gitea
docker compose up -d
```
Open `http://<host>:3000`. You get Gitea's **install wizard**, not a login page —
it writes `app.ini` from what you enter and that's the moment the instance is
configured. Two things to settle there rather than later:
1. **Server and Third-Party Service Settings → Base URL.** Set it to the URL
people will actually use. Wrong here means every clone URL it hands out is
wrong.
2. **Create the admin account** at the bottom of the wizard. If you skip it,
*the first user to register becomes the administrator* — which is fine on a
LAN and not fine on anything reachable.
## Ports
| Port | Proto | Purpose |
|------|-------|---------|
| 3000 | http | Web UI and the HTTP(S) clone endpoint |
| 222 | ssh | SSH clone endpoint (container listens on 22) |
## Why it looks like this
**It started as upstream's example** —
[Install with Docker](https://docs.gitea.com/installation/install-with-docker/)
— with four changes to bring it in line with the rest of this repo, listed at
the end of this section. Everything else is upstream's, and what follows is what
those choices actually mean.
**SQLite by default.** There's no database container here, and none is needed:
with no `GITEA__database__DB_TYPE` set, Gitea uses SQLite inside `/data`. For one
person or a small team that is genuinely the right answer — one less container,
one less backup, and the performance ceiling is far above what a personal
instance reaches. Upstream documents MySQL and PostgreSQL variants using
`GITEA__database__DB_TYPE=mysql` / `postgres`; switch if you outgrow it, and do
it before there's data to migrate.
**`USER_UID` / `USER_GID` exist because of the bind mount.** `./gitea:/data` is a
host directory, so the `git` user inside the container has to match whoever owns
it. Upstream notes that named volumes avoid the problem entirely — Docker manages
ownership — at the cost of your data being somewhere less obvious.
**SSH is on 222, not 22**, because 22 on the host belongs to the host's own sshd.
This is the source of the most common Gitea confusion: see the gotchas.
**Environment variables map to `app.ini` sections** with the pattern
`GITEA__section__KEY`. So `GITEA__server__ROOT_URL` sets `ROOT_URL` under
`[server]`. Useful to know, because the wizard only exposes a fraction of what
Gitea can be told.
### What was changed from upstream's example
Four things, all for the same reason — this file is published, and a fresh clone
has to work:
- **`${PUID:-1000}` / `${PGID:-1000}` instead of bare `${PUID}`.** Compose
interpolates an unset variable to an *empty string* and starts the container
anyway, so upstream's version hands Gitea `USER_UID=` on any clone without a
`.env`. Defaults are the convention here for exactly this reason.
- **`restart: unless-stopped` instead of `always`**, so a deliberate stop
survives a reboot.
- **`${DATA_PATH:-./data}` instead of `./gitea`**, which also stops the data
landing at the slightly comic `gitea/gitea/`.
- **Pinned to `1.27.3` instead of `:latest`**, because Gitea runs schema
migrations between releases.
Two settings are also present but commented out —
`GITEA__server__SSH_PORT` and `GITEA__server__ROOT_URL` — because both are
things you will need the moment you publish this beyond localhost. See the
gotchas.
## Gotchas
- **SSH clone URLs advertise the wrong port by default.** Gitea knows it listens
on 22 *inside* the container; it has no idea you published it on 222. So it
offers `git@host:user/repo.git`, which sends the client to port 22 — the host's
sshd — and the failure looks like a key problem rather than a port problem.
Uncomment `GITEA__server__SSH_PORT` in `compose.yaml` (it tracks `SSH_PORT`,
so the two can't drift), or set the SSH port in the wizard.
- **Behind a reverse proxy, `ROOT_URL` is the setting that matters.** Get it
wrong and the UI works fine while every clone URL, webhook and OAuth redirect
points somewhere else. See [reverse proxy](../docs/reverse-proxy.md), which
already lists Gitea as a service needing this.
- **The install wizard runs once.** After it writes `app.ini`, that file wins —
changing an environment variable afterwards does *not* change a setting the
wizard already recorded. Edit `gitea/gitea/conf/app.ini` (or delete it and
reinstall) if you need to change course.
- **First registered user becomes admin** if you didn't create an admin in the
wizard. Disable registration once your accounts exist:
`GITEA__service__DISABLE_REGISTRATION=true`.
- **Back up `./gitea`.** It holds every repository, the SQLite database, and
`conf/app.ini`. There is nothing else to back up, and nothing in it
regenerates.
- **Mirror pushes need credentials that outlive your session.** If you push from
here to GitHub, that's an access token or deploy key stored in Gitea, not your
laptop's agent.
## Exposing it
Publishes plain HTTP; TLS belongs at the proxy.
- [Reverse proxy with Caddy](../docs/reverse-proxy.md) — **needs `ROOT_URL`**,
see above. HTTP clone and the web UI both go through it.
- [Cloudflare Tunnel](../cloudflare-tunnel/) — works for the web UI and HTTP
clone, but **SSH clone won't pass** and the ~100 MB body cap will meet you at
the first large push. Fine for browsing, awkward as your only route.
- [Single sign-on with Authentik](../docs/authentik-sso.md) — Gitea speaks OIDC
natively, so use that rather than forward auth, which would break `git` over
HTTP as surely as it breaks any other non-browser client.
## Links
- Install with Docker: <https://docs.gitea.com/installation/install-with-docker/>
- Configuration cheat sheet: <https://docs.gitea.com/administration/config-cheat-sheet>
- Image: <https://hub.docker.com/r/gitea/gitea>