Gitea: Git self-hosted com Actions, runner e tea CLI

Tux em HQ diante do logo do Gitea e do losango do Git

Gitea (Git with a cup of tea) é a forge self-hosted em Go: repositório Git, PR, issues, wiki, registry de pacotes e CI/CD no mesmo binário. Fork do Gogs em 2016, MIT, cabe num VPS de 1 GB. A versão estável em setembro de 2026 é a 1.27.3. Este texto sobe a instância com Docker Compose e MariaDB, registra o Gitea Actions Runner e instala o CLI oficial tea.

O Git em si — BitKeeper, os dez dias de Linus, o 2.55 — está em A história do Git. O cheatsheet do dia a dia, em Git simples e rápido. Aqui o assunto é a forge: o GitHub que você roda na sua máquina.

O que o Gitea é (e o que não é)

Não é GitLab CE. Não puxa Elasticsearch, não pede 8 GB de RAM para o hello world. É um binário (ou um container) + um banco. O Gitea fala com SQLite3, MySQL/MariaDB, PostgreSQL e MSSQL. SQLite vem embutido e resolve lab, Raspberry Pi e um usuário só — o arquivo fica no volume /data. Em produção com time e Actions, use MariaDB (ou MySQL: o driver no Gitea é o mesmo, DB_TYPE=mysql). Actions desde 1.19 (ligado por default desde 1.21): o YAML é o da GitHub Actions, o executor é o runner próprio. O tea é o gh dessa casa.

Forgejo é o fork comunitário (2022) com a mesma API. Se o seu objetivo é “GitHub Actions em casa, leve”, Gitea 1.27 resolve. Código: github.com/go-gitea/gitea. Docs: docs.gitea.com.

Subir com Docker Compose e MariaDB

Imagem oficial: docker.gitea.com/gitea:1.27.3. Porta 3000 na web, 222 no host mapeada no SSH interno (22 do container) para não brigar com o sshd da máquina. MariaDB 11 no mesmo compose — o Gitea trata MariaDB como MySQL na config.

mkdir -p ~/gitea && cd ~/gitea
networks:
  gitea:

volumes:
  gitea-data:
  mariadb-data:

services:
  server:
    image: docker.gitea.com/gitea:1.27.3
    container_name: gitea
    restart: always
    environment:
      USER_UID: "1000"
      USER_GID: "1000"
      GITEA__database__DB_TYPE: mysql
      GITEA__database__HOST: db:3306
      GITEA__database__NAME: gitea
      GITEA__database__USER: gitea
      GITEA__database__PASSWD: gitea
    networks:
      - gitea
    volumes:
      - gitea-data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - db
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/"]
      interval: 10s
      timeout: 5s
      retries: 6
      start_period: 30s

  db:
    image: docker.io/library/mariadb:11
    restart: always
    environment:
      MARIADB_ROOT_PASSWORD: gitea
      MARIADB_DATABASE: gitea
      MARIADB_USER: gitea
      MARIADB_PASSWORD: gitea
    networks:
      - gitea
    volumes:
      - mariadb-data:/var/lib/mysql
    command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_bin"]
docker compose up -d
docker compose ps
docker compose logs -f server

Abra http://IP:3000 e complete o assistente. Hostname do banco: db (nome do serviço), não localhost. Tipo: MySQL (é o que a UI mostra para MariaDB). Depois do wizard o app.ini fica em /data/gitea/conf/app.ini no volume. Qualquer chave dá para sobrescrever com env no formato GITEA__secao__CHAVE.

Clone por SSH usa a porta 222:

git clone ssh://git@SEU_IP:222/usuario/repo.git

Chave pública: SSH sem senha. Em produção coloque HTTPS na frente (nginx/Caddy) e ajuste ROOT_URL no app.ini.

SQLite: um container, zero banco extra

Para testar na máquina ou num Pi, tire o serviço db e não passe GITEA__database__*. O Gitea cria sozinho um SQLite em /data/gitea/gitea.db.

services:
  server:
    image: docker.gitea.com/gitea:1.27.3
    container_name: gitea
    restart: always
    environment:
      USER_UID: "1000"
      USER_GID: "1000"
    volumes:
      - ./gitea-data:/data
    ports:
      - "3000:3000"
      - "222:22"

No assistente escolha SQLite3. Serve para um usuário e poucos repos. Com time, CI e registry, fique no MariaDB — SQLite trava o arquivo no dump concorrente e no job pesado.

Binário, se não quiser container

sudo wget -O /usr/local/bin/gitea \
  https://dl.gitea.com/gitea/1.27.3/gitea-1.27.3-linux-amd64
sudo chmod +x /usr/local/bin/gitea
sudo mkdir -p /var/lib/gitea /etc/gitea
sudo adduser --system --group --home /var/lib/gitea git
GITEA_WORK_DIR=/var/lib/gitea /usr/local/bin/gitea web -c /etc/gitea/app.ini

O binário já traz SQLite. MariaDB/MySQL e Postgres entram no app.ini ([database]DB_TYPE = mysql ou sqlite3). A partir da 1.27 os binários vêm com assinatura Sigstore. Lista e hashes: dl.gitea.com/gitea.

Gitea Actions e o Runner

O Gitea não executa o workflow sozinho. Quem puxa o job é o runner — o binário atual chama-se gitea-runner (o repositório antigo era act_runner; a imagem oficial em 2026 é docker.io/gitea/runner:3). Actions vêm ligadas por default desde 1.21. O YAML vive em .gitea/workflows/ e aceita o mesmo esquema da GitHub Actions.

Token: Site Administration → Actions → Runners → Create new runner token. Rode o runner em outra máquina se puder — ele come CPU/RAM do job, não da forge.

# no mesmo compose, ou noutro host
services:
  runner:
    image: docker.io/gitea/runner:3
    restart: always
    environment:
      GITEA_INSTANCE_URL: "http://server:3000"
      GITEA_RUNNER_REGISTRATION_TOKEN: "COLE_O_TOKEN"
      GITEA_RUNNER_NAME: "linuxpro-1"
    volumes:
      - ./runner-data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      server:
        condition: service_healthy

O tag :3 usa o Docker do host (monte o socket). Para isolamento, gitea/runner:3-dind sobe um dockerd próprio e pede --privileged. Persistência: o volume /data guarda o arquivo de registro (.runner). Sem ele, cada recreate vira um runner novo e o token antigo fica órfão na UI.

Workflow mínimo no repositório:

# .gitea/workflows/ci.yml
name: ci
on:
  push:
    branches: [main]
  pull_request:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo ok && uname -a

O contexto no YAML usa gitea.sha no lugar de github.sha quando for específico da forge; a maior parte das actions públicas funciona. CI/CD no sentido de pipeline: DevOps, o que é CI/CD?.

tea CLI

tea é o cliente oficial (MIT, Go). Issues, PR, release, login em várias instâncias, tea pr checkout no repositório local. A estável em agosto de 2026 é a 0.15.1.

sudo curl -fsSL -o /usr/local/bin/tea \
  https://dl.gitea.com/tea/0.15.1/tea-0.15.1-linux-amd64
sudo chmod +x /usr/local/bin/tea
tea --version

Token: Settings → Applications → Generate New Token (escopos: repo, issue, write:repository). Login:

tea login add --name casa --url http://SEU_IP:3000 --token SEU_TOKEN
tea login list
tea repos
tea issues
tea pulls

Dentro de um clone hospedado nessa instância o tea descobre o remote sozinho. Exemplos do dia:

tea issues list --state open
tea pulls checkout 12
tea pulls review 12 --approve --comment "LGTM"
tea releases create v1.0.0 --note "primeiro corte"

Desde 0.10 existe fluxo OAuth; token continua o caminho mais simples em servidor headless e em job de CI. Binários: dl.gitea.com/tea. Código: gitea.com/gitea/tea.

Backup e upgrade

docker compose exec -u git server gitea dump -c /data/gitea/conf/app.ini
# o zip cai em /data; copie para fora do volume

O dump inclui o banco (MariaDB via dump SQL, SQLite como arquivo). Upgrade: pin a tag no compose (1.27.3 → próxima), docker compose pull && docker compose up -d. Não troque rootful por rootless no mesmo volume. Não rode o dump com a instância no meio de migrate — pare, dump, suba a tag nova.

O que ficou

Gitea 1.27.3 + MariaDB 11 + runner 3 + tea 0.15.1 é uma forge completa em um VPS pequeno. SQLite para o primeiro docker compose up de tarde; MariaDB quando o time e o CI entram. O Git continua o de sempre; a camada social (PR, Actions, registry) é o que o GitHub cobrou e o Gogs não chegou a amarrar.

Mais: história do Git, cheatsheet, Gogs em 2017.