From 2f678693bf592e252baa518e4376df45e93f8099 Mon Sep 17 00:00:00 2001 From: Goyban Date: Thu, 3 Sep 2026 14:26:31 +0200 Subject: [PATCH] added Gitea --- gitea/.env.example | 34 ++++++++++++ gitea/README.md | 129 +++++++++++++++++++++++++++++++++++++++++++++ gitea/compose.yaml | 40 ++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 gitea/.env.example create mode 100644 gitea/README.md create mode 100644 gitea/compose.yaml diff --git a/gitea/.env.example b/gitea/.env.example new file mode 100644 index 0000000..3d812c8 --- /dev/null +++ b/gitea/.env.example @@ -0,0 +1,34 @@ +# Copy to .env and adjust. .env is git-ignored; this file is not. +# Every value here has a default in compose.yaml, so a fresh clone runs +# with no .env at all. + +# ─── Identity ─────────────────────────────────────────────────────── +# Become USER_UID / USER_GID in the container, which is how Gitea's `git` +# user is made to match the owner of the bind-mounted data directory. +# Wrong values mean Gitea cannot write its own data. +# +# id -u ; id -g +# +#PUID=1000 +#PGID=1000 + +# ─── Paths ────────────────────────────────────────────────────────── +# Repositories, the SQLite database and conf/app.ini. This is the whole +# backup -- nothing in it regenerates. +#DATA_PATH=./data + +# ─── Ports ────────────────────────────────────────────────────────── +#PORT=3000 + +# Host port for SSH clone; the container always listens on 22. Port 22 on +# the host belongs to the host's own sshd, which is why this isn't 22. +# +# If you change it, also uncomment GITEA__server__SSH_PORT in compose.yaml +# so the clone URLs Gitea advertises match -- otherwise it hands out +# port 22 and clients land on the host's sshd instead. +#SSH_PORT=222 + +# ─── Timezone ─────────────────────────────────────────────────────── +# There is no TZ variable here on purpose: compose.yaml mounts the host's +# /etc/timezone and /etc/localtime read-only, so the container follows the +# host automatically. diff --git a/gitea/README.md b/gitea/README.md new file mode 100644 index 0000000..dd5e436 --- /dev/null +++ b/gitea/README.md @@ -0,0 +1,129 @@ +# 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://: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: +- Configuration cheat sheet: +- Image: diff --git a/gitea/compose.yaml b/gitea/compose.yaml new file mode 100644 index 0000000..55e89ac --- /dev/null +++ b/gitea/compose.yaml @@ -0,0 +1,40 @@ +networks: + gitea: + external: false + +services: + server: + # Pinned. Gitea runs schema migrations between releases, so an unattended + # pull picking up a new minor is not something to meet by surprise. + image: docker.gitea.com/gitea:1.27.3 + container_name: gitea + restart: unless-stopped + networks: + - gitea + + environment: + # Gitea's `git` user is made to match the owner of the bind-mounted data + # directory. Defaults so a fresh clone runs with no .env at all -- bare + # ${PUID} would interpolate to an empty string and start anyway. + - USER_UID=${PUID:-1000} + - USER_GID=${PGID:-1000} + # Gitea knows it listens on 22 inside the container and has no idea it is + # published on 222, so the SSH clone URLs it hands out send clients to the + # host's own sshd. Uncomment to match the published port below. + #- GITEA__server__SSH_PORT=${SSH_PORT:-222} + # Set this once you're behind a proxy, or every clone URL, webhook and + # OAuth redirect points at the wrong host. + #- GITEA__server__ROOT_URL=https://git.example.com/ + + volumes: + # Every repository, the SQLite database and conf/app.ini. Back this up -- + # nothing in it regenerates. + - ${DATA_PATH:-./data}:/data + # Timezone comes from the host rather than a TZ variable. + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + ports: + - "${PORT:-3000}:3000" + # 22 on the host belongs to the host's own sshd, hence 222. + - "${SSH_PORT:-222}:22"