Gitea no Raspberry Pi: a história de como um GitLab derrubou meu homelab
Era uma sexta-feira à noite e eu estava satisfeito comigo mesmo. Tinha acabado de subir o GitLab Community Edition no meu Raspberry Pi 4B — um sonho antigo de ter um ambiente completo de desenvolvimento inteiramente sob o meu controle. Git, CI/CD, registry, tudo numa caixinha pequena rodando na estante.
Fui dormir.
No sábado de manhã, abri o terminal para fazer push de alguma coisa e encontrei… silêncio. O Raspberry Pi não respondia. SSH timeout. Ping nenhum. Fui até a estante, olhei para o LED verde piscando lentamente, e entendi: ele tinha reiniciado sozinho em algum momento da madrugada — e não tinha conseguido subir de volta por causa da pressão de memória que o GitLab deixou acumulada.
Esse foi o primeiro e mais doloroso capítulo da minha jornada com homelab.
Por que o GitLab afundou o Pi
O GitLab CE é uma obra de engenharia impressionante. É também uma obra que assume que você tem pelo menos 4GB de RAM disponíveis só pra ele — de preferência 8GB. O Raspberry Pi 4B tem 4GB no total, divididos entre sistema operacional, processos de background, e tudo mais que você queira rodar.
O stack do GitLab inclui: Puma (Ruby), Sidekiq, PostgreSQL próprio, Redis próprio, Gitaly, GitLab Workhorse, NGINX interno. Cada um desses processos tem footprint de memória considerável. Juntos, eles transformaram meu Pi num problema de scheduling de processos — irônico para um dispositivo que usei para estudar exatamente isso na faculdade.
A lição foi simples e cara: hardware modesto exige software modesto. Não dá pra pegar uma solução projetada para servidores e esperar que ela caiba num ARM com recursos contados.
Encontrando o Gitea
Depois de pesquisar alternativas, cheguei no Gitea. Escrito em Go. Binário único. Sem dependências externas além de um banco de dados. O processo inteiro — incluindo interface web, API, e gerenciamento de repositórios — rodando em menos de 150MB de RAM em idle.
Essa diferença não é acidental. É uma consequência direta da linguagem e da filosofia de design. Go compila para um binário estático, sem runtime externo, sem interpretador. O processo de boot é quase instantâneo, o footprint é previsível, e o comportamento em situações de pressão de memória é consideravelmente mais gentil do que uma aplicação Ruby que depende de dezenas de gems.
Subi o Gitea num Pod dentro do meu cluster k3s, com um PVC para persistência. O Pi não reiniciou mais.
A arquitetura atual
O ambiente que construí tem algumas camadas que se encaixam bem:
k3s no Raspberry Pi 4B como plataforma de orquestração. k3s é o Kubernetes para quem não tem data center — consomem menos de 500MB e sobe em segundos. Traefik já vem embutido como ingress controller, o que elimina uma peça inteira do stack.
Gitea como servidor Git e interface de CI. As organizações dentro do Gitea mapeiam diretamente para namespaces do Kubernetes — uma disciplina que mantém os contextos separados e facilita entender o que pertence a quem. org/project no Gitea corresponde ao namespace org no cluster.
Gitea Runner como executor de pipelines. Cada push dispara o runner, que roda os steps dentro de um container no próprio cluster. Sem agente externo, sem dependência de serviço cloud.
nerdctl + buildkitd para build das imagens. Substitui o Docker no ambiente do runner — o nerdctl fala a mesma API, mas se integra diretamente com o containerd do k3s. As imagens são construídas com cache local em disco e carregadas diretamente no namespace k8s.io do containerd, sem precisar de registry intermediário.
Traefik expondo os serviços via IngressRoute, com o Cloudflare Tunnel providenciando acesso externo sem abrir portas no roteador.
O problema do npm e a virada para Go
Até aqui, tudo razoavelmente controlado. O problema apareceu quando tentei integrar ao pipeline uma aplicação frontend com Node.js.
npm install numa aplicação moderna é uma experiência… particular. São centenas de pacotes, dependências transitivas, arquivos que se resolvem em tempo de instalação, scripts de build que podem fazer praticamente qualquer coisa. Num ambiente com disco lento (cartão SD) e CPU limitada (ARM Cortex-A72), um npm install + npm run build pode levar vários minutos — e consumir memória de forma que lembra desagradavelmente o GitLab.
O problema não é só performance. É imprevisibilidade. O node_modules de um projeto pode ter 500MB ou 2GB dependendo das dependências. O tempo de build varia com a rede, com a versão exata dos pacotes resolvida pelo lockfile, com flags de otimização. Cachear isso corretamente em ambiente com storage limitado é um problema por si só.
Foi aí que comecei a preferir Go para novos projetos.
A diferença é estrutural. Go é compilado estaticamente. O artefato final é um binário único, sem dependências de runtime. O go build é determinístico — dado o mesmo código e as mesmas dependências, produz o mesmo binário. O tempo de compilação é razoável mesmo em ARM. E o binário resultante ocupa megabytes, não gigabytes.
No runtime, um serviço Go típico consome dezenas de megabytes de RAM em idle. Um servidor Node.js com o mesmo propósito costuma partir de 100-200MB só para o processo do interpretador. Em hardware com restrições reais, essa diferença se traduz diretamente em quantos serviços você consegue rodar simultaneamente — e em quantas noites você vai dormir tranquilo sem o Pi reiniciar.
A tipagem estática do Go também contribui para o que chamo de determinismo comportamental: erros de tipo são capturados em compilação, não em runtime. Num pipeline de CI, isso significa que binários que passam pelo build têm um contrato mais forte com o ambiente que vão executar. Menos surpresas às três da manhã.
O que está rodando hoje
O ambiente atual hospeda:
- Sites estáticos em Hugo, com deploy automático via push
- Documentações em Docusaurus, buildadas e servidas como estático
- Serviços em Go com pipeline de build e deploy no cluster
- O próprio Gitea e sua infraestrutura
Tudo isso num Raspberry Pi 4B que cabe na palma da mão, consome menos de 15W, e não reiniciou mais desde que o GitLab saiu de cena.
Às vezes o melhor hardware upgrade é escolher o software certo.