Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04e84fde7a | |||
| 2f678693bf | |||
| cd1e3f836d | |||
| 50f9aff43a |
+198
-16
@@ -1,6 +1,8 @@
|
|||||||
# Single sign-on with Authentik
|
# Single sign-on with Authentik
|
||||||
|
|
||||||
TODO: fill in as you go. Outline below so the shape is decided in advance.
|
One login for everything self-hosted here, and one place to revoke it. The
|
||||||
|
services themselves stay unaware — nothing in any `compose.yaml` in this repo
|
||||||
|
mentions Authentik.
|
||||||
|
|
||||||
## Two ways to protect a service
|
## Two ways to protect a service
|
||||||
|
|
||||||
@@ -16,30 +18,210 @@ fallback.
|
|||||||
|
|
||||||
## Native OIDC — the shape
|
## Native OIDC — the shape
|
||||||
|
|
||||||
TODO: provider + application setup in Authentik, then the four values every
|
Use this wherever the app supports it. You get real accounts, real logout, and
|
||||||
app asks for:
|
clients that aren't browsers keep working.
|
||||||
|
|
||||||
- Issuer / discovery URL
|
### In Authentik
|
||||||
- Client ID
|
|
||||||
- Client secret → `.env`, never committed
|
|
||||||
- Redirect URI
|
|
||||||
|
|
||||||
## Forward auth — the shape
|
**Applications → Create**, provider type **OAuth2/OpenID Connect**. Three things
|
||||||
|
to note as you go:
|
||||||
|
|
||||||
TODO: the Caddy snippet, and which services you apply it to.
|
- the application **slug** — it appears in half the URLs below
|
||||||
|
- the **client ID** and **client secret**
|
||||||
|
- pick a **signing key**, or the app can't validate the token it gets back
|
||||||
|
|
||||||
|
Then add the **redirect URIs**, which is where this usually goes wrong — see the
|
||||||
|
gotchas.
|
||||||
|
|
||||||
|
### The four values every app asks for
|
||||||
|
|
||||||
|
| Value | Where it comes from |
|
||||||
|
|-------|--------------------|
|
||||||
|
| Issuer / discovery URL | `https://authentik.example.com/application/o/<slug>/` |
|
||||||
|
| Client ID | Authentik, on the provider |
|
||||||
|
| Client secret | Authentik → the service's `.env`, never committed |
|
||||||
|
| Redirect URI | The app tells you — don't guess |
|
||||||
|
|
||||||
|
Most apps take the issuer URL and discover the rest themselves; Audiobookshelf
|
||||||
|
calls this **Auto-populate**. When one wants the endpoints spelled out
|
||||||
|
individually:
|
||||||
|
|
||||||
|
| Endpoint | Path |
|
||||||
|
|----------|------|
|
||||||
|
| OpenID configuration | `/application/o/<slug>/.well-known/openid-configuration` |
|
||||||
|
| Authorization | `/application/o/authorize/` |
|
||||||
|
| Token | `/application/o/token/` |
|
||||||
|
| User info | `/application/o/userinfo/` |
|
||||||
|
| End session | `/application/o/<slug>/end-session/` |
|
||||||
|
| JWKS | `/application/o/<slug>/jwks/` |
|
||||||
|
|
||||||
|
Note which paths carry the slug and which don't: authorize, token and userinfo
|
||||||
|
are shared across every application, while discovery, JWKS and logout are
|
||||||
|
per-application. Putting the slug where it doesn't belong gives a 404 that looks
|
||||||
|
like the provider is broken.
|
||||||
|
|
||||||
|
Worked examples in this repo:
|
||||||
|
[audiobookshelf](../audiobookshelf/README.md#single-sign-on-with-authentik) and
|
||||||
|
[jellyfin](../jellyfin/README.md#single-sign-on-with-authentik).
|
||||||
|
|
||||||
|
## Forward auth — putting an app with no SSO behind Authentik
|
||||||
|
|
||||||
|
This is the fallback, and it's what most self-hosted apps need. The app itself
|
||||||
|
learns nothing and changes nothing: **Caddy asks Authentik whether to allow the
|
||||||
|
request before it ever reaches the container.** An app with no accounts at all,
|
||||||
|
or one with its own login you can't replace, ends up behind a real SSO login
|
||||||
|
page either way.
|
||||||
|
|
||||||
|
### In Authentik
|
||||||
|
|
||||||
|
1. **Providers → Create → Proxy Provider**, mode **Forward auth (single
|
||||||
|
application)**. The external host is the public URL — `https://firefly.domain.com`.
|
||||||
|
2. **Applications → Create**, and bind it to that provider.
|
||||||
|
3. **Outposts** — add the application to an outpost. The built-in *embedded
|
||||||
|
outpost* is the one already listening on port **9000**, which is what the
|
||||||
|
Caddyfile below points at.
|
||||||
|
|
||||||
|
Step 3 is the one that gets forgotten. Provider and application exist, the
|
||||||
|
Caddyfile is right, and every request still fails because the outpost was never
|
||||||
|
told the application is its responsibility.
|
||||||
|
|
||||||
|
### In Caddy
|
||||||
|
|
||||||
|
```caddyfile
|
||||||
|
firefly.domain.com {
|
||||||
|
# directive execution order is only as stated if enclosed with route.
|
||||||
|
route {
|
||||||
|
# always forward outpost path to actual outpost
|
||||||
|
reverse_proxy /outpost.goauthentik.io/* http://127.0.0.1:9000
|
||||||
|
|
||||||
|
# forward authentication to outpost
|
||||||
|
forward_auth http://127.0.0.1:9000 {
|
||||||
|
uri /outpost.goauthentik.io/auth/caddy
|
||||||
|
|
||||||
|
# capitalization of the headers is important, otherwise they will be empty
|
||||||
|
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
|
||||||
|
|
||||||
|
# optional, in this config trust all private ranges, should probably be set to the outposts IP
|
||||||
|
trusted_proxies private_ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
# actual site configuration below, for example
|
||||||
|
reverse_proxy 10.10.0.2:8564
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Only the last line changes per service: swap the address and port, and the
|
||||||
|
hostname at the top. Everything between is identical for every app.
|
||||||
|
|
||||||
|
`10.10.0.2` there is a **private address, not a public one** — in this setup a
|
||||||
|
WireGuard host, so Caddy reaches the app over the tunnel rather than across the
|
||||||
|
internet. Whatever address your proxy uses to reach the service goes there: a
|
||||||
|
LAN IP, a WireGuard or Tailscale peer, or a container name if Caddy runs in the
|
||||||
|
same Compose project. The only requirement is that Caddy can reach it and the
|
||||||
|
outside world can't.
|
||||||
|
|
||||||
|
### Why each piece is there
|
||||||
|
|
||||||
|
**`route { }` is not optional.** Caddy normally executes directives in its own
|
||||||
|
fixed order, not the order you wrote them. Without `route`, the final
|
||||||
|
`reverse_proxy` can run before `forward_auth`, and the app is served to
|
||||||
|
unauthenticated visitors. The config *looks* correct and protects nothing —
|
||||||
|
which is the worst possible failure mode for this feature, so wrap it and check
|
||||||
|
in a private window.
|
||||||
|
|
||||||
|
**The `/outpost.goauthentik.io/*` line comes first** because the login flow
|
||||||
|
itself lives on that path. If it fell through to the app, the browser would be
|
||||||
|
sent to a URL the app doesn't serve and the redirect loop would never resolve.
|
||||||
|
|
||||||
|
**`copy_headers` capitalization is load-bearing** — get it wrong and the headers
|
||||||
|
arrive empty rather than missing, so an app that reads them sees a blank
|
||||||
|
username instead of an error.
|
||||||
|
|
||||||
|
**`127.0.0.1:9000` assumes Caddy and Authentik are on the same host.** If Caddy
|
||||||
|
runs on a VPS and Authentik on the NAS, this is the line to change — and
|
||||||
|
`trusted_proxies private_ranges` should then name the outpost's actual address
|
||||||
|
rather than trusting every private range.
|
||||||
|
|
||||||
|
### What you give up
|
||||||
|
|
||||||
|
- **The app doesn't know who you are.** It sees an authenticated-by-someone
|
||||||
|
request. Apps with their own accounts still show their own login, so you get
|
||||||
|
**two logins** — Authentik, then the app. Some apps can read the
|
||||||
|
`X-Authentik-*` headers and skip theirs; most can't.
|
||||||
|
- **Non-browser clients break.** Mobile apps, API clients and anything doing
|
||||||
|
`curl` can't complete an interactive login flow. This is why Jellyfin and
|
||||||
|
Audiobookshelf use native OIDC instead — see
|
||||||
|
[jellyfin](../jellyfin/README.md) and
|
||||||
|
[audiobookshelf](../audiobookshelf/README.md). If a service has an API you
|
||||||
|
actually call, forward auth in front of it will stop that too.
|
||||||
|
- **It is all-or-nothing per hostname.** Every path behind that block requires
|
||||||
|
login, including webhooks and health checks.
|
||||||
|
|
||||||
|
### Where I use it
|
||||||
|
|
||||||
|
- **[Firefly III](../firefly/README.md)** — the config above is verbatim what
|
||||||
|
runs in front of it. Firefly keeps its own login underneath, so it's two
|
||||||
|
logins, and for bank data that's a trade worth making.
|
||||||
|
- **[copyparty](../copyparty/README.md)** and other apps with their own account
|
||||||
|
system, where the second login is the price of not exposing the app's login
|
||||||
|
form to the internet.
|
||||||
|
|
||||||
|
Prefer native OIDC wherever the app supports it. Reach for this when it doesn't.
|
||||||
|
|
||||||
## Groups and roles
|
## Groups and roles
|
||||||
|
|
||||||
TODO: how you map Authentik groups to per-app roles, and the admin/user
|
The mechanism is the same everywhere: Authentik puts your group membership in
|
||||||
split.
|
the token, and the app decides what that means.
|
||||||
|
|
||||||
|
**You usually don't need a custom mapping.** The standard `profile` scope
|
||||||
|
already includes group membership, so most apps see your groups with nothing
|
||||||
|
configured. Reach for a custom **scope mapping** (Customization → Property
|
||||||
|
Mappings) only when an app wants the claim under a different name or in a
|
||||||
|
different shape.
|
||||||
|
|
||||||
|
**Each app consumes it differently**, and that's the part to check per service —
|
||||||
|
Audiobookshelf has group and permission claim fields under its OpenID settings;
|
||||||
|
the Jellyfin SSO-Auth plugin has its own role and admin filters. There is no
|
||||||
|
single setting that makes "the admins group" mean administrator everywhere.
|
||||||
|
|
||||||
|
**The split worth setting up:** one group for ordinary access, one for admin,
|
||||||
|
and grant the app's admin role only to the second. The reason is the failure
|
||||||
|
mode below — if everyone who can log in is an admin, an SSO misconfiguration
|
||||||
|
isn't just an outage, it's an escalation.
|
||||||
|
|
||||||
|
TODO: the actual group names and per-app role mapping used here, once the same
|
||||||
|
shape has survived a few services.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
TODO: collect these as you hit them. Known ones worth writing down:
|
- **Keep a local fallback admin until the flow is proven.** If the only account
|
||||||
|
that can administer an app is behind SSO and SSO breaks, you have locked
|
||||||
- Locking yourself out of an app whose only admin is now behind SSO — keep a
|
yourself out of the thing you need in order to fix it. Prove the login works,
|
||||||
local fallback admin until the flow is proven.
|
in a private window, *before* removing the local account.
|
||||||
- Redirect URI mismatches, which usually surface as a generic error.
|
- **Redirect URI mismatches surface as a generic error** that never names the
|
||||||
|
URI. Read the exact URIs off the application's own settings page rather than
|
||||||
|
copying them from a guide — Audiobookshelf prints them, and
|
||||||
|
[its README](../audiobookshelf/README.md#the-redirect-uris-are-where-this-goes-wrong)
|
||||||
|
documents a case where the published guide was wrong for that install.
|
||||||
|
- **Behind TLS, apps build `http://` redirect URLs.** The proxy terminates TLS,
|
||||||
|
the app sees a plain HTTP request, and the callback it generates is insecure —
|
||||||
|
so the login fails halfway through. Every app has its own name for the fix;
|
||||||
|
Jellyfin's SSO-Auth plugin calls it
|
||||||
|
[Scheme Override](../jellyfin/README.md#the-setting-that-cost-me-the-time).
|
||||||
|
Firefly's `TRUSTED_PROXIES=**` is the same class of setting.
|
||||||
|
- **`email_verified` is `False` by default.** Some apps refuse to authenticate a
|
||||||
|
user whose email isn't marked verified, and the resulting error rarely says
|
||||||
|
so. Fix it in the scope mapping.
|
||||||
|
- **Forward auth breaks everything that isn't a browser.** Mobile apps and API
|
||||||
|
clients can't complete an interactive login. Check what talks to a service
|
||||||
|
before putting it behind forward auth — this is the single biggest reason to
|
||||||
|
prefer native OIDC.
|
||||||
|
- **Adding the application to an outpost is a separate step** from creating the
|
||||||
|
provider and the application, and forgetting it fails in a way that looks like
|
||||||
|
a Caddy problem.
|
||||||
|
- **The client secret belongs in the service's `.env`**, which is git-ignored.
|
||||||
|
Never in `compose.yaml`, which is committed.
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -221,9 +221,18 @@ behind a proxy.
|
|||||||
- [Reverse proxy with Caddy](../docs/reverse-proxy.md) — the default choice.
|
- [Reverse proxy with Caddy](../docs/reverse-proxy.md) — the default choice.
|
||||||
- [Cloudflare Tunnel](../cloudflare-tunnel/) — fine for this one. It's a text
|
- [Cloudflare Tunnel](../cloudflare-tunnel/) — fine for this one. It's a text
|
||||||
app; nothing here bumps the ~100 MB body cap unless you attach large files.
|
app; nothing here bumps the ~100 MB body cap unless you attach large files.
|
||||||
- [Single sign-on with Authentik](../docs/authentik-sso.md) — **think first.**
|
- [Single sign-on with Authentik](../docs/authentik-sso.md#forward-auth--putting-an-app-with-no-sso-behind-authentik)
|
||||||
This is your bank data, and Firefly's own login is the only thing in front of
|
— **what I do.** Firefly has no OIDC support, so it sits behind Authentik
|
||||||
it today. Keep a working local admin until any SSO flow is proven.
|
forward auth: Caddy checks with Authentik before the request reaches the
|
||||||
|
container. That means two logins, Authentik then Firefly, which for bank data
|
||||||
|
is a trade worth making — the app's own login form is never exposed to the
|
||||||
|
internet. The exact Caddyfile is in that doc.
|
||||||
|
|
||||||
|
Two things to keep in mind here specifically. Firefly's **API is behind the
|
||||||
|
same wall**, so external API clients stop working (the bundled importer is
|
||||||
|
unaffected — it talks to `app:8080` inside the compose network, never through
|
||||||
|
the proxy). And keep a working local admin until the flow is proven; locking
|
||||||
|
yourself out of your own ledger is an unpleasant afternoon.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
+129
@@ -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://<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>
|
||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user