{"id":502,"date":"2026-09-06T00:11:12","date_gmt":"2026-09-06T03:11:12","guid":{"rendered":"https:\/\/www.linuxpro.com.br\/2026\/09\/gitea-git-self-hosted-runner-tea\/"},"modified":"2026-10-05T14:37:03","modified_gmt":"2026-10-05T17:37:03","slug":"gitea-git-self-hosted-runner-tea","status":"publish","type":"post","link":"https:\/\/www.linuxpro.com.br\/en\/2026\/09\/gitea-git-self-hosted-runner-tea\/","title":{"rendered":"Gitea: self-hosted Git with Actions, runner, and tea CLI"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" alt=\"Mascote do LinuxPro encaixando um push de c\u00f3digo no servidor com o logo do Gitea, ao lado do banco MariaDB e do runner de CI com containers na esteira\" src=\"\/wp-content\/uploads\/2026\/09\/gitea-v4.webp\" width=\"1486\" height=\"856\" \/><\/p>\n<div style=\"background:#fff8e1;border-left:4px solid #8b1a1a;padding:.8em 1em;margin:1em 0\"><strong>Atualiza\u00e7\u00e3o (05\/10\/2026):<\/strong> guia revisado para o <strong>Gitea 28.0.0<\/strong>, de 29\/09\/2026 \u2014 a vers\u00e3o que abandonou o prefixo \u201c1.\u201d \u2014, e para o runner 4.1.0. Veja a se\u00e7\u00e3o <a href=\"#gitea-28\">O que muda na 28<\/a> antes de atualizar uma inst\u00e2ncia 1.27: h\u00e1 duas mudan\u00e7as incompat\u00edveis.<\/div>\n<p><strong>Gitea<\/strong> (<em>Git with a cup of tea<\/em>) \u00e9 a forge self-hosted em Go: reposit\u00f3rio Git, PR, issues, wiki, registry de pacotes e CI\/CD no mesmo bin\u00e1rio. Fork do <a href=\"\/2017\/04\/instalando-gogs-no-ubuntu\/\">Gogs<\/a> em 2016, licen\u00e7a MIT, cabe num VPS de 1 GB. A vers\u00e3o est\u00e1vel \u00e9 a <strong>28.0.0<\/strong>, de 29\/09\/2026. Este texto sobe a inst\u00e2ncia com Docker Compose e <strong>MariaDB<\/strong>, registra o <strong>Gitea Runner 4<\/strong> para rodar Actions e instala o CLI oficial <strong>tea 0.16<\/strong>.<\/p>\n<p><!-- more --><\/p>\n<p>O Git em si \u2014 BitKeeper, os dez dias de Linus, o 2.55 \u2014 est\u00e1 em <a href=\"\/2026\/09\/a-historia-do-git\/\">A hist\u00f3ria do Git<\/a>. O cheatsheet do dia a dia, em <a href=\"\/2017\/04\/git-simples-e-rapido\/\">Git simples e r\u00e1pido<\/a>. Aqui o assunto \u00e9 a forge: o GitHub que voc\u00ea roda na sua m\u00e1quina.<\/p>\n<h2>O que o Gitea \u00e9 (e o que n\u00e3o \u00e9)<\/h2>\n<p>N\u00e3o \u00e9 <a href=\"\/2026\/09\/a-historia-do-gitlab\/\">GitLab<\/a> CE. N\u00e3o puxa Elasticsearch, n\u00e3o pede 8 GB de RAM para o hello world. \u00c9 um bin\u00e1rio (ou um container) + um banco. O Gitea fala com SQLite3, MySQL\/MariaDB, PostgreSQL e MSSQL. <strong>SQLite<\/strong> vem embutido e resolve lab, Raspberry Pi e um usu\u00e1rio s\u00f3. Em produ\u00e7\u00e3o com time e Actions, use <strong>MariaDB<\/strong> (ou MySQL: o driver \u00e9 o mesmo, <code>DB_TYPE=mysql<\/code>).<\/p>\n<p>Actions existem desde a 1.19 e v\u00eam ligadas por padr\u00e3o desde a 1.21: o YAML \u00e9 o da GitHub Actions, mas quem executa \u00e9 um runner pr\u00f3prio, que voc\u00ea registra. O <code>tea<\/code> \u00e9 o <code>gh<\/code> dessa casa. O Forgejo \u00e9 o fork comunit\u00e1rio de 2022; a base \u00e9 comum, mas os dois projetos j\u00e1 divergem. C\u00f3digo: <a href=\"https:\/\/github.com\/go-gitea\/gitea\">github.com\/go-gitea\/gitea<\/a>. Docs: <a href=\"https:\/\/docs.gitea.com\/\">docs.gitea.com<\/a>.<\/p>\n<h2>Arquitetura<\/h2>\n<p>Tr\u00eas pe\u00e7as. O <strong>Gitea<\/strong> atende web, API e SSH e guarda os metadados no <strong>MariaDB<\/strong>. O <strong>runner<\/strong> pode ficar na mesma m\u00e1quina ou em outra: ele s\u00f3 abre conex\u00f5es de sa\u00edda para o Gitea, busca os jobs na fila e sobe um container por job no Docker. O ponto que mais derruba instala\u00e7\u00e3o nova \u00e9 o \u00faltimo passo: o container do job faz o checkout pela <code>ROOT_URL<\/code> da inst\u00e2ncia, ent\u00e3o ela precisa ser um endere\u00e7o que o job alcan\u00e7a \u2014 nunca <code>localhost<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" alt=\"Diagrama da arquitetura: desenvolvedor acessa o Gitea por HTTP 3000 e SSH 222, o Gitea usa MariaDB na porta 3306, o gitea-runner busca jobs no Gitea e sobe containers de job pelo Docker, que fazem checkout pela ROOT_URL\" src=\"\/wp-content\/uploads\/2026\/09\/gitea-arquitetura-runner.webp\" width=\"1200\" height=\"660\" \/><\/p>\n<h2 id=\"gitea-28\">O que muda na 28<\/h2>\n<p>A vers\u00e3o que viria a ser 1.28.0 saiu como <strong>28.0.0<\/strong>: o projeto <a href=\"https:\/\/blog.gitea.com\/release-of-28.0.0\/\">deixou de usar o prefixo \u201c1.\u201d<\/a>. N\u00e3o houve 27 vers\u00f5es perdidas \u2014 o n\u00famero que antes ficava depois do \u201c1.\u201d passou para a frente. Ajuste scripts que comparam vers\u00f5es ou que procuram tags <code>1.*<\/code>.<\/p>\n<p>Os destaques: <strong>log de auditoria<\/strong>, <strong>contas de bot<\/strong> gerenciadas pela interface, API e CLI, <strong>deploy tokens<\/strong> por HTTPS, <strong>impersona\u00e7\u00e3o<\/strong> de usu\u00e1rio pelo administrador, prote\u00e7\u00e3o de branch que bloqueia o merge enquanto houver revis\u00e3o pendente de <em>code owner<\/em>, filtros no diff e uma <strong>fila de builds<\/strong> para as Actions. As Actions tamb\u00e9m ganharam matriz din\u00e2mica, <code>max-parallel<\/code> e mais endpoints de API para gerenciar execu\u00e7\u00f5es e logs. A release traz corre\u00e7\u00f5es de seguran\u00e7a, como a rejei\u00e7\u00e3o de objetos Git inv\u00e1lidos ou duplicados no push e a garantia de que execu\u00e7\u00f5es de PR vindas de fork continuem atr\u00e1s da aprova\u00e7\u00e3o; os detalhes seriam publicados uma semana depois do lan\u00e7amento.<\/p>\n<p>Duas mudan\u00e7as s\u00e3o incompat\u00edveis e pedem aten\u00e7\u00e3o <em>antes<\/em> do upgrade:<\/p>\n<ul>\n<li><strong>Hist\u00f3rico de Actions passa a expirar.<\/strong> O novo <code>RUN_RETENTION_DAYS<\/code> apaga execu\u00e7\u00f5es conclu\u00eddas com mais de 400 dias, junto com jobs e tarefas, na limpeza da meia-noite. Para manter tudo, defina <code>RUN_RETENTION_DAYS = 0<\/code> na se\u00e7\u00e3o <code>[actions]<\/code> (no Compose, <code>GITEA__actions__RUN_RETENTION_DAYS: \"0\"<\/code>). Agora <code>0<\/code> significa \u201cguardar para sempre\u201d tamb\u00e9m em <code>LOG_RETENTION_DAYS<\/code> e <code>ARTIFACT_RETENTION_DAYS<\/code>, que antes apagavam tudo na limpeza seguinte com esse valor.<\/li>\n<li><strong>Tr\u00e1fego Git de sa\u00edda passa por um proxy interno.<\/strong> Migra\u00e7\u00f5es, espelhos e outras opera\u00e7\u00f5es de rede do Git agora obedecem \u00e0s regras de egress. O preset <code>external<\/code> deixou de existir: para negar tudo por padr\u00e3o, use <code>EGRESS_MODE = strict<\/code> e liste o que \u00e9 permitido (em <code>[migrations]<\/code> para migra\u00e7\u00f5es e espelhos; em <code>[security]<\/code> para webhooks e OAuth2). Curingas em IP e o item <code>*<\/code> n\u00e3o s\u00e3o mais aceitos, dom\u00ednios seguem a sintaxe do curl (<code>example.com<\/code> inclui os subdom\u00ednios; <code>*.example.com<\/code>, s\u00f3 os subdom\u00ednios) e uma entrada inv\u00e1lida em <code>BLOCKED_HOST_LIST<\/code> impede o Gitea de subir. <code>ALLOWED_DOMAINS<\/code>, <code>BLOCKED_DOMAINS<\/code> e <code>ALLOW_LOCALNETWORKS<\/code> ficam obsoletos em favor de <code>ALLOWED_HOST_LIST<\/code> e <code>BLOCKED_HOST_LIST<\/code>.<\/li>\n<\/ul>\n<p>Detalhe para quem baixa bin\u00e1rios por script: a 28 n\u00e3o publica mais builds x86 de 32 bits nem as variantes <code>gogit<\/code>, e os nomes de arquivo perderam o sufixo de vers\u00e3o do sistema operacional.<\/p>\n<h2>Subir com Docker Compose e MariaDB<\/h2>\n<p>Imagem oficial: <code>docker.gitea.com\/gitea:28.0.0<\/code> (testada aqui: sobe, cria o administrador pela CLI e responde <code>{\"version\":\"28.0.0\"}<\/code> em <code>\/api\/v1\/version<\/code>). Porta 3000 na web; 222 no host mapeada para o SSH do container (22), para n\u00e3o brigar com o sshd da m\u00e1quina. Troque <code>SEU_IP<\/code> pelo IP ou dom\u00ednio da m\u00e1quina e as senhas por valores seus.<\/p>\n<pre><code class=\"language-bash\">mkdir -p ~\/gitea &amp;&amp; cd ~\/gitea<\/code><\/pre>\n<pre><code class=\"language-yaml\"># docker-compose.yml\nnetworks:\n  gitea:\n\nvolumes:\n  gitea-data:\n  mariadb-data:\n\nservices:\n  server:\n    image: docker.gitea.com\/gitea:28.0.0\n    container_name: gitea\n    restart: always\n    environment:\n      USER_UID: \"1000\"\n      USER_GID: \"1000\"\n      GITEA__database__DB_TYPE: mysql\n      GITEA__database__HOST: db:3306\n      GITEA__database__NAME: gitea\n      GITEA__database__USER: gitea\n      GITEA__database__PASSWD: troque-esta-senha\n      GITEA__server__ROOT_URL: http:\/\/SEU_IP:3000\/\n      GITEA__server__SSH_DOMAIN: SEU_IP\n      GITEA__server__SSH_PORT: \"222\"\n    networks:\n      - gitea\n    volumes:\n      - gitea-data:\/data\n      - \/etc\/timezone:\/etc\/timezone:ro\n      - \/etc\/localtime:\/etc\/localtime:ro\n    ports:\n      - \"3000:3000\"\n      - \"222:22\"\n    depends_on:\n      - db\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http:\/\/localhost:3000\/\"]\n      interval: 10s\n      timeout: 5s\n      retries: 6\n      start_period: 30s\n\n  db:\n    image: docker.io\/library\/mariadb:11\n    restart: always\n    environment:\n      MARIADB_ROOT_PASSWORD: troque-esta-senha-root\n      MARIADB_DATABASE: gitea\n      MARIADB_USER: gitea\n      MARIADB_PASSWORD: troque-esta-senha\n    networks:\n      - gitea\n    volumes:\n      - mariadb-data:\/var\/lib\/mysql\n    command: [\"--character-set-server=utf8mb4\", \"--collation-server=utf8mb4_bin\"]<\/code><\/pre>\n<pre><code class=\"language-bash\">docker compose up -d\ndocker compose ps\ndocker compose logs -f server<\/code><\/pre>\n<p>Abra <code>http:\/\/SEU_IP:3000<\/code> e complete o assistente. Os campos do banco j\u00e1 v\u00eam preenchidos pelas vari\u00e1veis: host <code>db<\/code> (nome do servi\u00e7o, n\u00e3o localhost), tipo <strong>MySQL<\/strong> (\u00e9 como a UI chama o MariaDB). O primeiro usu\u00e1rio criado vira administrador. Depois do wizard o <code>app.ini<\/code> fica em <code>\/data\/gitea\/conf\/app.ini<\/code> no volume; qualquer chave pode ser sobrescrita por vari\u00e1vel no formato <code>GITEA__secao__CHAVE<\/code>.<\/p>\n<p><code>SSH_PORT: \"222\"<\/code> s\u00f3 muda a URL de clone que a interface mostra; o SSH continua escutando na 22 dentro do container. Clone:<\/p>\n<pre><code class=\"language-bash\">git clone ssh:\/\/git@SEU_IP:222\/usuario\/repo.git<\/code><\/pre>\n<p>Chave p\u00fablica: <a href=\"\/2017\/04\/ssh-sem-senha\/\">SSH sem senha<\/a>. Em produ\u00e7\u00e3o coloque HTTPS na frente (nginx ou <a href=\"\/2026\/10\/caddy-no-linux-servidor-web-com-https-automatico\/\">Caddy<\/a>), troque a <code>ROOT_URL<\/code> para <code>https:\/\/git.seudominio\/<\/code> e, se ningu\u00e9m de fora deve criar conta, defina <code>GITEA__service__DISABLE_REGISTRATION: \"true\"<\/code>.<\/p>\n<h2>SQLite: um container, zero banco extra<\/h2>\n<p>Para testar na m\u00e1quina ou num Pi, tire o servi\u00e7o <code>db<\/code> e as vari\u00e1veis <code>GITEA__database__*<\/code>. No assistente escolha <strong>SQLite3<\/strong>; o arquivo fica em <code>\/data\/gitea\/gitea.db<\/code>.<\/p>\n<pre><code class=\"language-yaml\">services:\n  server:\n    image: docker.gitea.com\/gitea:28.0.0\n    container_name: gitea\n    restart: always\n    environment:\n      USER_UID: \"1000\"\n      USER_GID: \"1000\"\n    volumes:\n      - .\/gitea-data:\/data\n    ports:\n      - \"3000:3000\"\n      - \"222:22\"<\/code><\/pre>\n<p>Serve para um usu\u00e1rio e poucos repos. Com time, CI e registry, fique no MariaDB: o SQLite serializa as escritas e engasga quando v\u00e1rios jobs e usu\u00e1rios gravam ao mesmo tempo.<\/p>\n<h2>Bin\u00e1rio com systemd, se n\u00e3o quiser container<\/h2>\n<p>Caminho da documenta\u00e7\u00e3o oficial, com o Git do sistema e um usu\u00e1rio <code>git<\/code> dedicado:<\/p>\n<pre><code class=\"language-bash\">sudo apt install -y git\nsudo wget -O \/usr\/local\/bin\/gitea \\\n  https:\/\/dl.gitea.com\/gitea\/28.0.0\/gitea-28.0.0-linux-amd64\nsudo chmod +x \/usr\/local\/bin\/gitea\n\nsudo adduser --system --shell \/bin\/bash --gecos 'Git Version Control' \\\n  --group --disabled-password --home \/home\/git git\nsudo mkdir -p \/var\/lib\/gitea\/{custom,data,log} \/etc\/gitea\nsudo chown -R git:git \/var\/lib\/gitea\nsudo chmod -R 750 \/var\/lib\/gitea\nsudo chown root:git \/etc\/gitea\nsudo chmod 770 \/etc\/gitea   # o assistente web grava o app.ini aqui<\/code><\/pre>\n<pre><code class=\"language-ini\"># \/etc\/systemd\/system\/gitea.service\n[Unit]\nDescription=Gitea\nAfter=network.target mariadb.service\n\n[Service]\nUser=git\nGroup=git\nWorkingDirectory=\/var\/lib\/gitea\/\nExecStart=\/usr\/local\/bin\/gitea web --config \/etc\/gitea\/app.ini\nEnvironment=USER=git HOME=\/home\/git GITEA_WORK_DIR=\/var\/lib\/gitea\nRestart=always\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<pre><code class=\"language-bash\">sudo systemctl daemon-reload\nsudo systemctl enable --now gitea\n# depois do assistente, trave a configura\u00e7\u00e3o:\nsudo chmod 750 \/etc\/gitea\nsudo chmod 640 \/etc\/gitea\/app.ini<\/code><\/pre>\n<p>Mais sobre units e <code>systemctl<\/code>: <a href=\"\/2026\/09\/dominando-o-systemd-comandos-essenciais\/\">Dominando o systemd<\/a>. O bin\u00e1rio j\u00e1 traz SQLite; MariaDB\/MySQL e PostgreSQL entram no <code>[database]<\/code> do <code>app.ini<\/code>. Cada bin\u00e1rio em <a href=\"https:\/\/dl.gitea.com\/gitea\/\">dl.gitea.com\/gitea<\/a> vem com <code>.sha256<\/code>, assinatura GPG (<code>.asc<\/code>) e pacote Sigstore (<code>.sigstore.json<\/code>) \u2014 confira antes de colocar em produ\u00e7\u00e3o.<\/p>\n<h2>Gitea Actions e o Runner<\/h2>\n<p>O Gitea n\u00e3o executa workflow sozinho. Quem pega o job \u00e9 o <strong>runner<\/strong>: o bin\u00e1rio chama-se <code>gitea-runner<\/code> (o projeto antes se chamava <code>act_runner<\/code>), o c\u00f3digo est\u00e1 em <a href=\"https:\/\/gitea.com\/gitea\/runner\">gitea.com\/gitea\/runner<\/a> e a imagem oficial \u00e9 <code>docker.io\/gitea\/runner<\/code>. A vers\u00e3o atual \u00e9 a <strong>4.1.0<\/strong>, de 01\/10\/2026, que acrescentou um backend para executar os jobs no Kubernetes. O YAML vive em <code>.gitea\/workflows\/<\/code> e usa o mesmo esquema da GitHub Actions.<\/p>\n<p>Token de registro: <em>Site Administration \u2192 Actions \u2192 Runners \u2192 Create new runner<\/em> (vale tamb\u00e9m por organiza\u00e7\u00e3o ou por reposit\u00f3rio, nas configura\u00e7\u00f5es de cada um). Se puder, rode o runner em outra m\u00e1quina \u2014 o job consome CPU e RAM de quem o executa, e a forge n\u00e3o deve disputar recurso com um build.<\/p>\n<pre><code class=\"language-yaml\"># acrescente em services: no mesmo docker-compose.yml\n  runner:\n    image: docker.io\/gitea\/runner:4\n    restart: always\n    environment:\n      GITEA_INSTANCE_URL: http:\/\/SEU_IP:3000\/\n      GITEA_RUNNER_REGISTRATION_TOKEN: COLE_O_TOKEN\n      GITEA_RUNNER_NAME: linuxpro-1\n    networks:\n      - gitea\n    volumes:\n      - .\/runner-data:\/data\n      - \/var\/run\/docker.sock:\/var\/run\/docker.sock\n    depends_on:\n      server:\n        condition: service_healthy<\/code><\/pre>\n<p><code>GITEA_INSTANCE_URL<\/code> deve ser a mesma <code>ROOT_URL<\/code> do Gitea. D\u00e1 para registrar com <code>http:\/\/server:3000<\/code> pela rede do compose, mas o checkout dentro do job usa a <code>ROOT_URL<\/code>, e o container do job n\u00e3o resolve o nome <code>server<\/code>.<\/p>\n<p>A imagem vem em tr\u00eas sabores:<\/p>\n<ul>\n<li><code>gitea\/runner:4<\/code> \u2014 s\u00f3 o runner; usa o Docker do host pelo socket montado. Simples, mas quem monta o <code>docker.sock<\/code> tem o equivalente a root na m\u00e1quina, e os jobs enxergam os outros containers dela.<\/li>\n<li><code>gitea\/runner:4-dind<\/code> \u2014 traz um dockerd pr\u00f3prio, isolado do host; exige <code>privileged: true<\/code> e n\u00e3o usa o socket.<\/li>\n<li><code>gitea\/runner:4-dind-rootless<\/code> \u2014 mesmo esquema, com o daemon rodando como UID 1000; menos poder, mais limita\u00e7\u00f5es de rede e cgroups.<\/li>\n<\/ul>\n<p>O volume <code>\/data<\/code> guarda o arquivo de registro <code>.runner<\/code>. Sem ele, cada recria\u00e7\u00e3o do container registra um runner novo e o antigo fica \u00f3rf\u00e3o na UI. Esse volume n\u00e3o guarda o cache de imagens: no <code>dind<\/code>, monte tamb\u00e9m <code>\/var\/lib\/docker<\/code> se n\u00e3o quiser baixar tudo de novo a cada restart.<\/p>\n<p>Quem vem da s\u00e9rie 3 deve ler as notas da 4.0: com <code>cache.external_server<\/code> os jobs passam a falar com o cache atrav\u00e9s do runner, jobs em <code>container.network: bridge<\/code> perdem acesso ao cache e aos artefatos, e <code>*<\/code> em <code>valid_volumes<\/code> n\u00e3o atravessa mais <code>\/<\/code> (use <code>**<\/code>). A 4.0 tamb\u00e9m trouxe a action <code>builtin:checkout<\/code>, que n\u00e3o precisa de download nem de Node na imagem do job.<\/p>\n<p>Workflow m\u00ednimo no reposit\u00f3rio:<\/p>\n<pre><code class=\"language-yaml\"># .gitea\/workflows\/ci.yml\nname: ci\non:\n  push:\n    branches: [main]\n  pull_request:\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - run: echo \"commit ${{ gitea.sha }}\" &amp;&amp; uname -a<\/code><\/pre>\n<p>O label <code>ubuntu-latest<\/code> vem por padr\u00e3o e aponta para a imagem <code>docker.gitea.com\/runner-images:ubuntu-latest<\/code>. O contexto aceita <code>gitea.*<\/code> e tamb\u00e9m <code>github.*<\/code>, ent\u00e3o a maior parte das actions p\u00fablicas funciona sem mudan\u00e7a; <code>uses: actions\/checkout@v4<\/code> \u00e9 baixado do GitHub, porque <code>DEFAULT_ACTIONS_URL<\/code> vale <code>github<\/code> por padr\u00e3o \u2014 em rede isolada, espelhe as actions na pr\u00f3pria inst\u00e2ncia. Pipeline em conceito: <a href=\"\/2019\/12\/devops-ci-cd\/\">DevOps: o que \u00e9 CI\/CD?<\/a>.<\/p>\n<h2>tea CLI<\/h2>\n<p><code>tea<\/code> \u00e9 o cliente oficial (MIT, Go): issues, PR, releases, labels, login em v\u00e1rias inst\u00e2ncias e <code>tea pr checkout<\/code> no reposit\u00f3rio local. A est\u00e1vel \u00e9 a <strong>0.16.0<\/strong>, de 10\/09\/2026, que trouxe <code>tea login status<\/code> e leitura da descri\u00e7\u00e3o de issue ou PR a partir de arquivo ou stdin.<\/p>\n<pre><code class=\"language-bash\">sudo curl -fsSL -o \/usr\/local\/bin\/tea \\\n  https:\/\/dl.gitea.com\/tea\/0.16.0\/tea-0.16.0-linux-amd64\nsudo chmod +x \/usr\/local\/bin\/tea\ntea --version<\/code><\/pre>\n<p>Token: <em>Settings \u2192 Applications \u2192 Generate New Token<\/em>, com permiss\u00e3o de leitura e escrita em repository e issue. Login:<\/p>\n<pre><code class=\"language-bash\">tea login add --name casa --url http:\/\/SEU_IP:3000 --token SEU_TOKEN\ntea login list\ntea repos\ntea issues\ntea pulls<\/code><\/pre>\n<p>Dentro de um clone hospedado nessa inst\u00e2ncia o <code>tea<\/code> descobre o remote sozinho. Exemplos do dia:<\/p>\n<pre><code class=\"language-bash\">tea issues list --state open\ntea pulls checkout 12\ntea pulls approve 12 \"LGTM\"\ntea pulls merge 12\ntea releases create --tag v1.0.0 --title v1.0.0 --note \"primeiro corte\"<\/code><\/pre>\n<p>Para <code>git push<\/code> por HTTPS sem senha, <code>tea login add<\/code> aceita <code>--git-credentials<\/code> e se registra como credential helper do Git. Em esta\u00e7\u00e3o com navegador existe o fluxo <code>--oauth<\/code>; em servidor headless e em job de CI, token continua o caminho simples. Bin\u00e1rios: <a href=\"https:\/\/dl.gitea.com\/tea\/\">dl.gitea.com\/tea<\/a>. C\u00f3digo: <a href=\"https:\/\/gitea.com\/gitea\/tea\">gitea.com\/gitea\/tea<\/a>.<\/p>\n<h2>Backup e upgrade<\/h2>\n<p>O <code>gitea dump<\/code> empacota reposit\u00f3rios, configura\u00e7\u00e3o, anexos e um dump SQL do banco num zip. No container ele precisa rodar como <code>git<\/code> e dentro de um diret\u00f3rio grav\u00e1vel:<\/p>\n<pre><code class=\"language-bash\">docker compose exec -u git -w \/tmp server \\\n  gitea dump -c \/data\/gitea\/conf\/app.ini --file \/tmp\/gitea-dump.zip\ndocker compose cp server:\/tmp\/gitea-dump.zip .\/gitea-dump-$(date +%F).zip\ndocker compose exec server rm \/tmp\/gitea-dump.zip<\/code><\/pre>\n<p>N\u00e3o existe comando de restore: a volta \u00e9 manual (descompactar, devolver arquivos e importar o SQL). Por isso vale ter tamb\u00e9m o dump nativo do banco \u2014 <code>mariadb-dump<\/code> \u2014 agendado e testado; o <a href=\"\/2026\/09\/databasus-backup-de-postgresql-mysql-e-mariadb-com-restore-testado\/\">Databasus<\/a> faz isso com restaura\u00e7\u00e3o verificada.<\/p>\n<p>Upgrade da 1.27 para a 28, na ordem:<\/p>\n<ol>\n<li>Fa\u00e7a o <code>gitea dump<\/code> e o dump nativo do banco.<\/li>\n<li>Decida a reten\u00e7\u00e3o das Actions: se quiser manter todo o hist\u00f3rico, acrescente <code>GITEA__actions__RUN_RETENTION_DAYS: \"0\"<\/code> ao servi\u00e7o <code>server<\/code> <em>antes<\/em> de subir a nova vers\u00e3o.<\/li>\n<li>Se voc\u00ea usa <code>ALLOWED_DOMAINS<\/code>, <code>BLOCKED_DOMAINS<\/code>, <code>ALLOW_LOCALNETWORKS<\/code> ou o preset <code>external<\/code>, reescreva as regras com <code>ALLOWED_HOST_LIST<\/code>, <code>BLOCKED_HOST_LIST<\/code> e <code>EGRESS_MODE<\/code>.<\/li>\n<li>Troque a tag no compose (<code>1.27.3<\/code> \u2192 <code>28.0.0<\/code>) e rode <code>docker compose pull &amp;&amp; docker compose up -d<\/code>. As migra\u00e7\u00f5es do banco rodam na subida.<\/li>\n<li>Confira <code>docker compose logs server<\/code>: o Gitea avisa no in\u00edcio quando encontra regra de egress inv\u00e1lida ou lista de hosts sem <code>EGRESS_MODE<\/code> expl\u00edcito.<\/li>\n<\/ol>\n<p>N\u00e3o troque a imagem rootful pela rootless no mesmo volume.<\/p>\n<h2>O que ficou<\/h2>\n<p>Gitea 28 + MariaDB 11 + runner 4.1 + tea 0.16 \u00e9 uma forge completa num VPS pequeno. SQLite para o primeiro <code>docker compose up<\/code>; MariaDB quando o time e o CI entram. Acerte a <code>ROOT_URL<\/code> antes de registrar o runner e metade dos problemas de Actions some. O Git continua o de sempre; a camada social \u2014 PR, Actions, registry \u2014 \u00e9 o que o GitHub cobrou e o Gogs n\u00e3o chegou a amarrar.<\/p>\n<p>Mais: <a href=\"\/2026\/09\/a-historia-do-git\/\">hist\u00f3ria do Git<\/a>, <a href=\"\/2026\/09\/a-historia-do-gitlab\/\">hist\u00f3ria do GitLab<\/a>, <a href=\"\/2026\/09\/a-historia-do-docker\/\">hist\u00f3ria do Docker<\/a>, <a href=\"\/2017\/04\/git-simples-e-rapido\/\">cheatsheet do Git<\/a> e o <a href=\"\/2017\/04\/instalando-gogs-no-ubuntu\/\">Gogs em 2017<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Gitea is the self-hosted forge in Go: Git, PR, issues, registry, and CI\/CD in a single binary. Updated guide for Gitea 28.0.0 \u2014 the version that dropped the \u201c1.\u201d prefix \u2014, with Docker Compose and MariaDB, 4.1 runner, tea CLI, and the upgrade roadmap from 1.27.<\/p>","protected":false},"author":0,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[45,46,2,111,120],"tags":[105,156,48,201,204,203,202],"class_list":["post-502","post","type-post","status-publish","format-standard","hentry","category-desenv","category-devops","category-linux","category-opensource","category-servidores","tag-ci-cd","tag-docker","tag-git","tag-gitea","tag-runner","tag-self-hosted","tag-tea"],"_links":{"self":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/502","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/comments?post=502"}],"version-history":[{"count":7,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/502\/revisions"}],"predecessor-version":[{"id":1800,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/502\/revisions\/1800"}],"wp:attachment":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/media?parent=502"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/categories?post=502"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/tags?post=502"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}