Gitea Gitea

[SSO] Self-hosted Git service

Gitea is a lightweight, open-source self-hosted Git service. It’s a community-managed fork of Gogs with enhanced features focused on Git management and CI/CD integration.

Installation: The following commands will output a new SECRET_KEY and INTERNAL_TOKEN to stdout, which you can then place in your environment variables.

docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret SECRET_KEY
docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret INTERNAL_TOKEN

Do not lose/change your SECRET_KEY after the installation, otherwise the encrypted data can not be decrypted anymore.

TODO

TODO: Generate SECRET_KEY and INTERNAL_TOKEN for enhanced security TODO: Consider switching to PostgreSQL for improved performance with large repositories

References

Authentik

Gitea uses native OIDC for Authentik’s developers group. Browser SSO does not replace Git-over-SSH keys, Git HTTPS personal access tokens, or runner tokens. Keep a local Gitea administrator for recovery.

Setup

Create the ignored secret file at config/docker/<host>/gitea/authentik-client-secret with mode 0600. Set GITEA_AUTHENTIK_CLIENT_ID, GITEA_AUTHENTIK_CLIENT_SECRET, and GITEA_AUTHENTIK_SIGNING_KEY in the ignored host .env file. The signing key is an asymmetric Authentik certificate/key UUID.

Create the Authentik resources, recreate Gitea, then configure its persisted authentication source:

scripts/authentik-apps.py --application gitea --apply
scripts/labctl.py service recreate dev/gitea
docker/dev/gitea/gitea-auth.py --host <host> --apply

Run the last command again after changing OIDC client settings. Keep the registered callback URI exact: https://gitea.<domain>/user/oauth2/authentik/callback.

Verify

Confirm a developers member can sign in, a non-member is denied by Authentik, an existing account links only through the intended flow, and local administrator, Git SSH, and PAT workflows remain usable.

Backup

backup.yaml exports SQLite online each night under ${DB_BACKUP_ROOT}/dev-gitea/. Pair the DB with /data/git, /data/gitea and /data/ssh from the verified application B2 snapshot, including repositories, LFS, attachments, SSH keys and the original app.ini/secret key. See the backup guide for commands, retention and snapshot selection.

Recovery

  1. Retrieve database.sqlite3 and its manifest plus the paired repositories, LFS, attachments, action artifacts, avatars, packages, JWT/SSH state and app.ini. The database alone is not a repository backup.
  2. Stop the destination and install the standalone export as /data/gitea/gitea.db, removing stale destination WAL/SHM. Restore paired /data/git, /data/gitea and /data/ssh files with the expected ownership.
  3. Recover original GITEA_SECRET_KEY, GITEA_INTERNAL_TOKEN, OIDC secret and persisted configuration; do not generate fresh installation secrets. Start the matching image before testing access.

DB/repository capture is not atomic; check refs, LFS and attachment references. Keep runners, webhooks and outbound notifications disconnected on a test restore.

Post-restore check

Restore the original config/secret key and paired repositories with the DB. Sign in, browse a known commit, and clone one repository using an existing SSH key or PAT. Check one LFS object if used.

name: gitea
services:
  gitea:
    image: gitea/gitea:28.2.0@sha256:30777c91280d973fa02be48b1ade735bd6859e8f6f7af3b4444d519437852504
    container_name: gitea
    restart: unless-stopped
    environment:
      TZ: ${TIMEZONE}
      PUID: ${PUID}
      PGID: ${PGID}
      USER_UID: ${PUID}
      USER_GID: ${PGID}
      # Server configuration
      GITEA__server__DOMAIN: gitea.${MYDOMAIN}
      GITEA__server__ROOT_URL: https://gitea.${MYDOMAIN}/
      # SSH configuration
      GITEA__server__SSH_DOMAIN: gitea.${MYDOMAIN}
      GITEA__server__SSH_PORT: 222
      GITEA__server__SSH_LISTEN_PORT: 22
      # Security
      GITEA__security__SECRET_KEY: ${GITEA_SECRET_KEY}
      GITEA__security__INTERNAL_TOKEN: ${GITEA_INTERNAL_TOKEN}
      # Authentik OIDC permits developer accounts while retaining local-admin recovery.
      # Onboard once: configure ignored GITEA_AUTHENTIK_* values and secret file,
      # run `scripts/authentik-apps.py --apply`, recreate Gitea,
      # then run `docker/dev/gitea/gitea-auth.py --host <host> --apply`. Re-run the last
      # command after changing the client secret, client ID, or OIDC source settings.
      GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "true"
      GITEA__service__DISABLE_REGISTRATION: "false"
      GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
      GITEA__oauth2_client__USERNAME: preferred_username
      GITEA__oauth2_client__ACCOUNT_LINKING: login
      GITEA__oauth2_client__OPENID_CONNECT_SCOPES: profile email
    volumes:
      - ${DOCKER_VOLUMES}/gitea:/data
    networks:
      - proxy
    ports:
      - "222:22"  # SSH port for Git operations
    labels:
      traefik.enable: true
      traefik.http.routers.gitea.rule: Host(`gitea.${MYDOMAIN}`)
      traefik.http.routers.gitea.middlewares: localaccess@file
      traefik.http.services.gitea.loadbalancer.server.port: 3000
      homepage.group: Development
      homepage.name: Gitea
      homepage.icon: gitea.png
      homepage.href: https://gitea.${MYDOMAIN}/
      homepage.description: "[SSO] Self-hosted Git service"

networks:
  proxy:
    external: true