Tutoriais

Gitea

Tutorial completo de instalação e operação do Gitea — git self-hosted, Actions, container registry, SSO via OAuth2, mirroring e backup.

Tutorial de referência para rodar uma instância Gitea em servidor self-hosted atrás de reverse proxy, com Postgres externo, SSH em porta não-padrão, SSO via Keycloak, CI nativo (Actions + act_runner) e container registry embutido.

Versões alvo: Gitea 1.22.x / 1.23.x (imagens gitea/gitea:1.22-rootless ou mais recente). Os comandos e variáveis seguem o Config Cheat Sheet oficial.


1. Visão geral

Gitea é uma forja Git self-hosted escrita em Go. Roda como um único binário (ou container) com SQLite, MySQL ou PostgreSQL como banco. Comparado às alternativas:

AspectoGitHub.comGitLab CEGitea
Custo de operaçãoZero (SaaS)Pesado: ~4 GB RAM mínimo, Ruby/RailsLeve: ~200-500 MB RAM, binário Go
CI nativoActionsGitLab CIActions (compatível com sintaxe do GitHub)
Container registryghcr.ioembutidoembutido (OCI)
LFS / Packagessimsimsim
SSO OIDCempresarialsimsim
Mirror push/pulllimitadosimsim

Quando vale self-host:

  • Repositórios privados sem cota
  • Dados ficam no seu hardware/jurisdição
  • Integração direta com a stack interna (Keycloak, registry, monitoramento)
  • Custo previsível (um servidor modesto dá conta de dezenas de devs)

Quando não vale: se você precisa de comunidade pública, discoverability ou pipelines de CI massivos rodando em runners gerenciados, o GitHub continua ganhando por preço/conveniência.


2. Arquitetura

Fluxo geral da infra-alvo deste tutorial:

flowchart LR
    user[Dev / Browser]
    subgraph Server[Servidor Debian 13]
        direction TB
        nginx[nginx-proxy<br/>TLS termination]
        subgraph net[rede proxy 172.28.0.0/16]
            gitea[gitea container<br/>:3000 HTTP<br/>:2222 SSH]
            pg[(postgres17:5432)]
            runner[act_runner]
        end
        vol[/data/gitea<br/>repos + LFS + attachments + packages/]
    end

    user -- HTTPS 443 --> nginx
    nginx -- HTTP 3000 --> gitea
    user -- SSH :2222 --> gitea
    gitea --> pg
    gitea --- vol
    runner -- long poll HTTP --> gitea

Pontos importantes:

  • SSH em 2222 é publicada direto pelo host (NAT pra container). Não passa por nginx — nginx é HTTP/HTTPS apenas.
  • Reverse proxy termina TLS e injeta X-Forwarded-*. O Gitea precisa saber disso via ROOT_URL para gerar URLs corretas em e-mails/webhooks.
  • Storage: por padrão tudo (repos, LFS, attachments, avatars, packages) fica em /data/gitea. Pode ser separado por seção do app.ini.
  • Registry embutido roda no próprio Gitea no endpoint /v2/. Não é um serviço separado.
  • Actions runner é um processo externo que faz long-polling no Gitea — precisa de network compartilhada com o container Gitea para resolver o hostname interno.

Fluxo de um push SSH:

sequenceDiagram
    participant Dev
    participant nginx
    participant Host as Servidor (iptables/NAT)
    participant Gitea
    participant PG as Postgres

    Dev->>Host: ssh -p 2222 git@gitea.example.com
    Host->>Gitea: forward TCP 2222 -> container :2222
    Gitea->>PG: SELECT public_key WHERE fingerprint=...
    PG-->>Gitea: ok / user_id
    Gitea-->>Dev: handshake ok
    Dev->>Gitea: git-receive-pack repo.git
    Gitea->>Gitea: pre-receive hook (LFS/branch protection)
    Gitea->>PG: INSERT activity, commits
    Gitea-->>Dev: success

3. Pré-requisitos

  • Host: Debian 13 (ou compatível) com Docker Engine 24+ e Docker Compose v2
  • Postgres 17 já rodando e acessível (por exemplo, container postgres17 na mesma rede proxy)
  • DNS apontando para o servidor: gitea.example.com A <IP-do-servidor>
  • Portas abertas no firewall:
    • 443 (HTTPS via nginx)
    • 2222 (SSH Git)
  • nginx-proxy com certificado TLS válido (Let’s Encrypt, etc.)
  • SMTP opcional para notificações (Postmark, Resend, Mailgun, ou outro)

Verifique a network existente:

docker network ls | grep proxy
docker network inspect proxy --format '{{.IPAM.Config}}'

Crie o banco e o usuário no Postgres (uma vez):

docker exec -it postgres17 psql -U postgres -c "CREATE USER gitea WITH PASSWORD '<sua-senha-forte>';"
docker exec -it postgres17 psql -U postgres -c "CREATE DATABASE gitea OWNER gitea ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C' TEMPLATE template0;"

Nota: use uma senha forte e aleatória. Caso precise trocá-la depois, veja o bloco de rotação de senha na seção de upgrade.


4. Instalação com Docker Compose

Existem duas variantes oficiais da imagem:

  • gitea/gitea:1.22 — root, expõe SSH no próprio container via OpenSSH.
  • gitea/gitea:1.22-rootlessrecomendada. Roda como UID 1000, usa o servidor SSH interno (Go), menor superfície de ataque. Volume em /var/lib/gitea e config em /etc/gitea.

Bloco do docker-compose.yaml (extrato em /opt/docker/docker-compose.yaml):

services:
  gitea:
    image: gitea/gitea:1.22-rootless
    container_name: gitea
    restart: unless-stopped
    user: "1000:1000"
    networks:
      - proxy
    depends_on:
      - postgres17
    volumes:
      - /data/gitea/data:/var/lib/gitea
      - /data/gitea/config:/etc/gitea
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      # SSH publicado direto no host (NAT). HTTP fica interno na proxy.
      - "2222:2222"
    environment:
      # ---- Identificação do servidor ----
      GITEA__server__DOMAIN: gitea.example.com
      GITEA__server__ROOT_URL: https://gitea.example.com/
      GITEA__server__SSH_DOMAIN: gitea.example.com
      GITEA__server__HTTP_PORT: "3000"
      # SSH_PORT = porta pública anunciada nas URLs git@host:port; SSH_LISTEN_PORT = porta dentro do container.
      GITEA__server__SSH_PORT: "2222"
      GITEA__server__SSH_LISTEN_PORT: "2222"
      GITEA__server__START_SSH_SERVER: "true"
      GITEA__server__LFS_START_SERVER: "true"
      GITEA__server__OFFLINE_MODE: "true"

      # ---- Banco ----
      GITEA__database__DB_TYPE: postgres
      GITEA__database__HOST: postgres17:5432
      GITEA__database__NAME: gitea
      GITEA__database__USER: gitea
      GITEA__database__PASSWD: ${GITEA_DB_PASSWORD}
      GITEA__database__SSL_MODE: disable

      # ---- Cadastro de usuários ----
      # Mantenha aberto durante o setup inicial pra criar o admin via web;
      # depois feche e use Keycloak.
      GITEA__service__DISABLE_REGISTRATION: "true"
      GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "true"
      GITEA__service__SHOW_REGISTRATION_BUTTON: "false"

      # ---- Webhooks ----
      # Por padrão Gitea bloqueia hosts privados; libere se webhooks vão
      # falar com serviços internos da sua infraestrutura.
      GITEA__webhook__ALLOWED_HOST_LIST: external,*.example.com,172.28.0.0/16

      # ---- Indexer ----
      GITEA__indexer__ISSUE_INDEXER_TYPE: bleve
      GITEA__indexer__REPO_INDEXER_ENABLED: "true"
      GITEA__indexer__REPO_INDEXER_TYPE: bleve

      # ---- Actions ----
      GITEA__actions__ENABLED: "true"

      # ---- USER UID/GID (rootless usa 1000) ----
      USER_UID: "1000"
      USER_GID: "1000"

networks:
  proxy:
    external: true

Crie o .env ao lado do compose com:

GITEA_DB_PASSWORD=<sua-senha-forte>

Permissões do volume (rootless precisa que o UID 1000 do host seja dono):

sudo mkdir -p /data/gitea/{data,config}
sudo chown -R 1000:1000 /data/gitea

Suba:

docker compose -f /opt/docker/docker-compose.yaml up -d gitea
docker logs -f gitea

A primeira boot vai criar o schema no Postgres e gerar chaves SSH do servidor em /var/lib/gitea/git/.ssh/.


5. Setup inicial via web

Acesse https://gitea.example.com/install.

Mesmo com env vars setadas, a tela /install aparece uma vez para você confirmar e criar o primeiro admin. Pontos críticos:

  1. Database settings: já vem preenchido a partir do env. Confirme.
  2. General settings: confirme Server Domain e Gitea Base URL.
  3. Optional settings → Administrator Account:
    • Crie o admin agora, antes de fechar /install. Se você só fechar sem criar, o primeiro usuário a se registrar via web vira admin (com DISABLE_REGISTRATION=true isso fica difícil, mas evite ambiguidade).
  4. Clique em Install Gitea. O Gitea grava INSTALL_LOCK=true no app.ini e nunca mais reabre /install.

Alternativa CLI (se preferir não usar a web):

docker exec -u 1000 gitea gitea admin user create \
  --username admin \
  --password '<sua-senha-forte>' \
  --email admin@example.com \
  --admin --must-change-password=true

6. Configuração avançada do app.ini

Depois do install, o arquivo gerado fica em /data/gitea/config/gitea/app.ini (dentro do container: /etc/gitea/app.ini).

Toda config via env var GITEA__section__KEY substitui o app.ini no boot. Você pode editar diretamente o app.ini OU usar env vars — o padrão recomendado é env vars no compose para tudo que muda por ambiente, e deixar o app.ini só com defaults e segredos gerados (SECRET_KEY, INTERNAL_TOKEN).

Seções principais

[server]
DOMAIN           = gitea.example.com
ROOT_URL         = https://gitea.example.com/
SSH_DOMAIN       = gitea.example.com
HTTP_PORT        = 3000
SSH_PORT         = 2222
SSH_LISTEN_PORT  = 2222
START_SSH_SERVER = true
LFS_START_SERVER = true
OFFLINE_MODE     = true

[database]
DB_TYPE  = postgres
HOST     = postgres17:5432
NAME     = gitea
USER     = gitea
PASSWD   = `<sua-senha-forte>`
SSL_MODE = disable

[security]
INSTALL_LOCK       = true
SECRET_KEY         = `gerado-no-install-NÃO-MEXER`
INTERNAL_TOKEN     = `gerado-no-install-NÃO-MEXER`
PASSWORD_HASH_ALGO = pbkdf2_v2
MIN_PASSWORD_LENGTH = 12
DISABLE_GIT_HOOKS  = true

[mailer]
ENABLED   = true
PROTOCOL  = smtps
SMTP_ADDR = smtp.postmarkapp.com
SMTP_PORT = 465
USER      = `token-postmark`
PASSWD    = `token-postmark`
FROM      = "Gitea <gitea@example.com>"

[oauth2]
JWT_SECRET = `gerado-NÃO-MEXER`

[lfs]
PATH             = /var/lib/gitea/lfs
HTTP_AUTH_EXPIRY = 24h
MAX_FILE_SIZE    = 1073741824   ; 1 GiB

[indexer]
ISSUE_INDEXER_TYPE   = bleve
REPO_INDEXER_ENABLED = true
REPO_INDEXER_TYPE    = bleve
; Para repos muito grandes (>50 GB de código indexado) considere elasticsearch:
; ISSUE_INDEXER_TYPE = elasticsearch
; ISSUE_INDEXER_CONN_STR = http://elasticsearch:9200

[service]
DISABLE_REGISTRATION             = true
ALLOW_ONLY_EXTERNAL_REGISTRATION = true
REGISTER_EMAIL_CONFIRM           = false
ENABLE_NOTIFY_MAIL               = true

[webhook]
ALLOWED_HOST_LIST = external,*.example.com,172.28.0.0/16
DELIVER_TIMEOUT   = 10

[actions]
ENABLED              = true
DEFAULT_ACTIONS_URL  = github

Notas sobre segurança

  • SECRET_KEY e INTERNAL_TOKEN: gerados aleatoriamente no install. Nunca rode dois Giteas com o mesmo SECRET_KEY — eles vão competir pela cifragem de tokens 2FA/oauth.
  • PASSWORD_HASH_ALGO = pbkdf2_v2: o padrão pbkdf2 é OK; argon2 é mais forte mas usa mais memória.
  • DISABLE_GIT_HOOKS = true: impede que usuários (não-admin) instalem git hooks no servidor — manter.
  • INSTALL_LOCK = true: trava /install. Se precisar reabrir (e.g. troca de DB), seta false e reinicia, mas faça backup antes.

Aplicar mudanças: docker compose restart gitea. Mudanças em [server] ou [database] exigem restart; mudanças em [ui] ou [mailer] na maioria dos casos também.


7. Integração com Keycloak (OAuth2 SSO)

7.1 Criar o client no Keycloak

No realm que você usa (exemplo: corporate):

  1. Clients → Create client
    • Client type: OpenID Connect
    • Client ID: gitea
    • Name: Gitea
  2. Capability config:
    • Client authentication: On (vai virar confidential)
    • Standard flow: On
    • Direct access grants: Off
  3. Login settings:
    • Root URL: https://gitea.example.com
    • Valid redirect URIs: https://gitea.example.com/user/oauth2/keycloak/callback
    • Web origins: https://gitea.example.com
  4. Salvar. Aba Credentials → copiar o Client Secret.
  5. Client scopes → gitea-dedicated (ou crie um scope email/profile se ainda não tiver mapper de email): garanta que o token inclui email, email_verified, preferred_username, name.

URL de discovery (anote):

https://auth.example.com/realms/corporate/.well-known/openid-configuration

7.2 Adicionar Auth Source no Gitea

Via UI: Site Administration → Identity & Access → Authentication Sources → Add Authentication Source.

  • Authentication Type: OAuth2
  • Authentication Name: keycloak (este nome vira parte do callback URL — por isso o redirect URI acima usa /keycloak/callback)
  • OAuth2 Provider: OpenID Connect
  • Client ID (Key): gitea
  • Client Secret: <secret copiado>
  • OpenID Connect Auto Discovery URL: https://auth.example.com/realms/corporate/.well-known/openid-configuration
  • Additional Scopes: openid profile email
  • Required Claim Name / Value: deixe em branco a menos que queira filtrar por role.
  • Claim providing group names for this source: groups (se você quiser mapear roles do Keycloak para teams do Gitea)
  • Map claims to admin / restricted: opcional, via Admin Group Claim Value.

Via CLI:

docker exec -u 1000 gitea gitea admin auth add-oauth \
  --name keycloak \
  --provider openidConnect \
  --key gitea \
  --secret 'CLIENT_SECRET' \
  --auto-discover-url 'https://auth.example.com/realms/corporate/.well-known/openid-configuration' \
  --scopes 'openid profile email'

7.3 Sync de email/nome/auto-registro

No app.ini (ou via env):

[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
USERNAME                 = preferred_username  ; ou "email"
UPDATE_AVATAR            = true
ACCOUNT_LINKING          = auto                ; auto liga se email bate

Com isso, o primeiro login via Keycloak cria a conta Gitea automaticamente, linkando por email se já existir conta local.


8. SSH

8.1 Chaves do servidor

No modo rootless, o sshd interno do Gitea (Go) gera as host keys em:

/var/lib/gitea/git/.ssh/gitea.{rsa,ecdsa,ed25519}

Faça backup desses arquivos no primeiro boot — se forem perdidos, todos os clients vão receber warning de “host key changed”.

Listar:

docker exec -u 1000 gitea ls -la /var/lib/gitea/git/.ssh/

8.2 Porta 2222 no cliente

Como o SSH não está na 22, configure o cliente em ~/.ssh/config:

Host gitea.example.com
    HostName gitea.example.com
    Port 2222
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

A partir daí, git clone git@gitea.example.com:<usuario>/<projeto>.git funciona sem -p 2222.

Cadastre sua chave pública em https://gitea.example.com/user/settings/keys.

Teste:

ssh -T git@gitea.example.com
# Hi there, <usuario>! You've successfully authenticated...

9. Reverse proxy nginx

Server block para gitea.example.com (em apps/infra/nginx/conf.d/gitea.conf):

upstream gitea_backend {
    server gitea:3000;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name gitea.example.com;

    ssl_certificate     /etc/nginx/certs/gitea.example.com.crt;
    ssl_certificate_key /etc/nginx/certs/gitea.example.com.key;

    # Upload de repos / LFS / imagens de container podem ser grandes
    client_max_body_size 2048M;

    # Push grande / git-lfs / clone inicial precisa de timeout generoso
    proxy_read_timeout    600s;
    proxy_send_timeout    600s;
    proxy_connect_timeout 30s;

    location / {
        proxy_pass http://gitea_backend;
        proxy_http_version 1.1;

        # Websocket upgrade (Actions logs streaming + live updates)
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $http_connection;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Redirect HTTP -> HTTPS
server {
    listen 80;
    server_name gitea.example.com;
    return 301 https://$host$request_uri;
}

Se a instância deve ficar restrita ao range da VPN/rede confiável, adicione no server block uma allowlist:

allow 10.8.0.0/24;        # rede da VPN
allow 192.168.1.0/24;     # LAN
deny  all;

Recarregar nginx:

docker exec nginx-proxy nginx -t && docker exec nginx-proxy nginx -s reload

10. Gitea Actions

10.1 Habilitar no servidor

No app.ini (ou env var):

[actions]
ENABLED              = true
DEFAULT_ACTIONS_URL  = github

DEFAULT_ACTIONS_URL=github significa que uses: actions/checkout@v4 será resolvido contra github.com/actions/checkout. Você também pode apontar para um mirror interno (e.g. self).

Reinicia o container.

10.2 Obter o token de registro

Três escopos possíveis:

EscopoURL
Instânciahttps://gitea.example.com/-/admin/actions/runners
Organizaçãohttps://gitea.example.com/<org>/-/settings/actions/runners
Repositóriohttps://gitea.example.com/<owner>/<repo>/settings/actions/runners

Clique em Create new Runner e copie o token. Alternativamente via CLI:

docker exec -u 1000 gitea gitea actions generate-runner-token

10.3 Subir act_runner via docker compose

Adicione no mesmo docker-compose.yaml:

  gitea-runner:
    image: gitea/act_runner:latest
    container_name: gitea-runner
    restart: unless-stopped
    networks:
      - proxy
    depends_on:
      - gitea
    environment:
      CONFIG_FILE: /config.yaml
      GITEA_INSTANCE_URL: https://gitea.example.com
      GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
      GITEA_RUNNER_NAME: runner-01
      GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bullseye,docker:docker://docker:dind"
    volumes:
      - /data/gitea/runner/config.yaml:/config.yaml
      - /data/gitea/runner/data:/data
      - /var/run/docker.sock:/var/run/docker.sock

Gere um config.yaml base:

docker run --rm gitea/act_runner:latest generate-config > /data/gitea/runner/config.yaml

Subir:

RUNNER_TOKEN=xxxxx docker compose up -d gitea-runner
docker logs -f gitea-runner

Você deve ver runner registered successfully e depois polling logs. Confirme em /-/admin/actions/runners que ele aparece como Idle.

10.4 Workflow de exemplo

.gitea/workflows/ci.yml (sintaxe igual GitHub Actions):

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install
        run: npm ci

      - name: Test
        run: npm test

      - name: Build container
        run: |
          docker build -t gitea.example.com/${{ gitea.repository }}:${{ gitea.sha }} .

      - name: Login to registry
        run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login gitea.example.com -u ${{ gitea.actor }} --password-stdin

      - name: Push
        run: docker push gitea.example.com/${{ gitea.repository }}:${{ gitea.sha }}

Secrets ficam em Settings → Actions → Secrets (escopo repo ou org). REGISTRY_TOKEN deve ser um Personal Access Token com escopo write:package.


11. Container Registry

O Gitea expõe um registry OCI em https://gitea.example.com/v2/.

11.1 Criar PAT

Settings → Applications → Generate New Token. Selecione apenas os escopos mínimos:

  • read:package
  • write:package (se for fazer push)

11.2 Login / push / pull

echo "$PAT" | docker login gitea.example.com -u <usuario> --password-stdin

docker tag minha-app:dev gitea.example.com/<usuario>/minha-app:1.0.0
docker push gitea.example.com/<usuario>/minha-app:1.0.0

docker pull gitea.example.com/<usuario>/minha-app:1.0.0

O namespace pode ser usuário ou organização (gitea.example.com/<owner>/<image>).

Listar pacotes na UI: https://gitea.example.com/<owner>/-/packages.

11.3 Registry embutido vs registry dedicado (Nexus / Harbor)

CritérioGitea registryNexus / registry dedicado
Setupzero (vem junto)container separado
RBACherda do Gitea (org/repo)políticas próprias
Proxy/cache de Docker Hubnãosim (Nexus pull-through)
Limpeza/retention policiesmanual / via APIsim, sofisticado
Multi-tenant pesadoOK até dezenas de imagensmelhor pra centenas

Regra prática: imagens da própria org (build artifact de um repo) ficam no Gitea registry — mantém auditoria junto do código. Imagens operacionais/infra (bases, mirrors de upstream) ficam num registry dedicado (registry.example.com, geralmente Nexus ou Harbor) onde tem retention policies e pull-through cache.


12. Mirroring e webhooks

12.1 Push mirror para o GitHub

Em Settings → Mirror Settings do repositório:

  • Mirror From URL: vazio (este é push mirror)
  • Push Mirror → Add Push Mirror
    • Git Remote URL: https://github.com/<usuario>/<projeto>.git
    • Authorization → Username: seu user GitHub
    • Password: PAT do GitHub com repo scope
    • Interval: 8h0m0s

A cada intervalo, Gitea faz git push --mirror. Útil pra ter um espelho público sem usar o GitHub como primário.

12.2 Webhooks para CI externo

Settings → Webhooks → Add Webhook → Gitea (genérico HTTP JSON):

  • Target URL: https://ci.exemplo.com/hook
  • HTTP Method: POST
  • Content Type: application/json
  • Secret: chave aleatória (vai vir no header X-Gitea-Signature)
  • Trigger On: Push, Pull Request, Release

Importante: se o destino estiver em rede privada, ele precisa estar em GITEA__webhook__ALLOWED_HOST_LIST (veja seção 4).


13. Backup

13.1 gitea dump (oficial)

A doc oficial pede para parar o Gitea para garantir consistência entre DB e repos. Em ambientes pequenos com pouca atividade, dump quente costuma funcionar — mas para backup confiável, pare.

# Pare o gitea (opcional mas recomendado)
docker compose stop gitea

# Crie o dump (rode dentro do container, como UID 1000)
docker compose run --rm -u 1000 -v /data/backups:/backup gitea \
  gitea dump -c /etc/gitea/app.ini --file /backup/gitea-$(date +%Y%m%d).zip

docker compose start gitea

O .zip resultante contém:

  • app.ini + diretório custom/
  • Dump SQL do banco (XORM, em gitea-db.sql)
  • Repos completos (repos/)
  • Anexos, avatars, LFS
  • Logs (opcional)

13.2 Alternativa: pg_dump + tar do volume

Mais rápido e suporta backup quente bem:

# Banco
docker exec postgres17 pg_dump -U gitea -F c gitea > /data/backups/gitea-db-$(date +%F).dump

# Volume (LFS e repos)
tar --use-compress-program=zstd -cf /data/backups/gitea-data-$(date +%F).tar.zst -C /data/gitea data

Para restaurar:

# Banco
docker exec -i postgres17 pg_restore -U gitea -d gitea --clean --if-exists < gitea-db-2026-05-11.dump

# Dados
docker compose stop gitea
tar -xf gitea-data-2026-05-11.tar.zst -C /data/gitea
docker compose start gitea

# Regerar hooks (obrigatório se você moveu de path/instalação)
docker exec -u 1000 gitea gitea admin regenerate hooks

13.3 Automação

Cron job no host (/etc/cron.d/gitea-backup):

0 3 * * * root /usr/local/bin/gitea-backup.sh >> /var/log/gitea-backup.log 2>&1

E rotação simples (find /data/backups -mtime +14 -delete).


14. Upgrade

Gitea segue semver. Migrações de schema rodam automaticamente no boot.

Procedimento

  1. Leia o changelog em https://blog.gitea.com/. Procure por “breaking changes” ou “config rename”.

  2. Backup completo (seção 13). Sem isso, downgrade entre minors é impossível.

  3. Bump da tag no docker-compose.yaml:

    image: gitea/gitea:1.23-rootless   # antes: 1.22-rootless
  4. Pull e recreate:

    docker compose pull gitea
    docker compose up -d gitea
    docker logs -f gitea
  5. Acompanhe os logs até “Listen: http://0.0.0.0:3000”. Em DBs grandes, migrações podem levar alguns minutos.

Rollback

  • Patch (1.22.0 → 1.22.1): seguro voltar a tag, schema é o mesmo.
  • Minor (1.22 → 1.23): schema muda. Só funciona restaurando o backup do banco anterior. Sem backup, você está preso na nova versão.

Rotação de senha do banco

Procedimento para trocar a senha do usuário do banco:

# 1. Trocar no Postgres
docker exec -it postgres17 psql -U postgres -c "ALTER USER gitea WITH PASSWORD '<nova-senha-forte>';"

# 2. Atualizar .env
sed -i 's/^GITEA_DB_PASSWORD=.*/GITEA_DB_PASSWORD=<nova-senha-forte>/' /opt/docker/.env

# 3. Recreate (só envvars novas, não precisa pull)
docker compose up -d gitea

O Gitea reabre o pool com a senha nova no próximo boot.


15. Troubleshooting

15.1 fatal: the remote end hung up unexpectedly em push grande

Aumente os timeouts no nginx (já feito na seção 9) e no Gitea:

[server]
PROXY_PROTOCOL_HEADER_TIMEOUT = 5s

[http]
; nada — Go default é generoso

Push de >100 MB? Use Git LFS em vez de blob puro.

15.2 Permission denied (publickey) em SSH

Cheque na ordem:

  1. Sua chave pública está em https://gitea.example.com/user/settings/keys?

  2. O cliente está usando a chave certa?

    ssh -vT git@gitea.example.com

    Procure por Offering public key: ~/.ssh/id_ed25519.

  3. A porta 2222 está aberta?

    nc -zv gitea.example.com 2222
  4. As host keys do servidor não foram regeradas? Se sim, remova a entrada antiga em ~/.ssh/known_hosts.

15.3 Permissão negada no volume (rootless)

Erros como chown /var/lib/gitea: permission denied no boot:

sudo chown -R 1000:1000 /data/gitea

O UID 1000 é hardcoded na imagem rootless — não tente rodar com outro UID via user: se não souber o que está fazendo.

15.4 Runner Actions stuck em “Idle” mas não pega job

  • Confirme que o container gitea-runner está na mesma network (proxy) do gitea.

  • Confirme GITEA_INSTANCE_URL aponta para o hostname externo HTTPS (não use http://gitea:3000 em produção — o runner usa essa URL pra callback de logs e o cert TLS importa).

  • Verifique labels no workflow vs labels do runner. Se o workflow pede runs-on: ubuntu-latest e seu runner só tem self-hosted, ninguém pega.

  • Logs:

    docker logs --tail 200 gitea-runner

15.5 Webhook falhando com “Resolved IP is not allowed”

Hostname destino não está em ALLOWED_HOST_LIST. Adicione a env var (GITEA__webhook__ALLOWED_HOST_LIST) e reinicie.

15.6 gitea dump falha com “permission denied”

Está rodando como root mas o volume é owned por 1000. Use docker compose run -u 1000 (veja seção 13).

15.7 /install não abre depois de mudar o DB

INSTALL_LOCK=true no app.ini. Edite manualmente para false, reinicie, refaça o install.


16. Referências