No description
Find a file
Pablo 8e8a5de989 A forge: Forgejo e Caddy na VM, com o arranque completo
Separa a forge do repositório da aplicação. Esta VM é o único recurso do
projeto que não nasce do OpenTofu — é onde o OpenTofu vai rodar — e por
isso o compose, o Caddyfile e o cloud-init daqui são a única forma escrita
do procedimento que a reconstrói. Deixá-los apenas na VM é circular: a
forge hospedaria o repositório que descreve a forge.

O runner sai junto, para me/runner: ele roda em outra máquina, porque o
pico de RAM de um job passa de 3 GB e esta VM tem 2. É o que permite ficar
na BV1-2-40.

O README passa a registrar o que o repositório não cobre. forgejo-data e
caddy-data não estão aqui e não devem estar, então nada disto é backup —
o compose recria a forge vazia. Fica o procedimento de cópia, com o stop
antes do tar, porque SQLite copiado quente só se revela inconsistente na
hora de restaurar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015BmUugDy3EBa7XihKRaCMq
2026-08-29 15:19:02 -03:00
.env.example A forge: Forgejo e Caddy na VM, com o arranque completo 2026-08-29 15:19:02 -03:00
.gitignore A forge: Forgejo e Caddy na VM, com o arranque completo 2026-08-29 15:19:02 -03:00
Caddyfile A forge: Forgejo e Caddy na VM, com o arranque completo 2026-08-29 15:19:02 -03:00
cloud-init.yaml A forge: Forgejo e Caddy na VM, com o arranque completo 2026-08-29 15:19:02 -03:00
docker-compose.yml A forge: Forgejo e Caddy na VM, com o arranque completo 2026-08-29 15:19:02 -03:00
README.md A forge: Forgejo e Caddy na VM, com o arranque completo 2026-08-29 15:19:02 -03:00

A forge

Forgejo atrás do Caddy, numa VM da Magalu criada à mão. É o único recurso do projeto que não nasce do OpenTofu, porque é onde o OpenTofu vai rodar.

O runner não está aqui. Ele roda em outra máquina, por conta do pico de RAM de cada job, e mora em me/runner. Esta VM só hospeda o git e a interface.

A ordem dos dois primeiros passos inverte a numeração de propósito: o DNS precisa de um IP para apontar, então a VM vem primeiro e o registro.br logo em seguida — antes do Caddy subir, porque ele só emite certificado depois que o nome resolve.

1. A VM, no console da Magalu (br-se1)

Console → Virtual Machines → criar instância.

Região: br-se1, decidido. O documento de arquitetura ainda fixa br-ne1; ficou o Sudeste, que tem três zonas de disponibilidade contra as duas do Nordeste e onde a colocação por zona está liberada — é o que permite DBaaS multi-zona no passo 3. Falta propagar a escolha para o documento: o backend do OpenTofu e o endpoint do Object Storage (https://br-se1.magaluobjects.com) mudam junto, e têm de mudar antes do primeiro tofu apply — depois dele custa migrar state e bucket.

Campo Valor Por quê
Imagem Ubuntu 24.04 LTS Base do cloud-init.yaml.
Tipo BV1-2-40 — 1 vCPU / 2 GB / 40 GB R$ 69,99/mês. Ver a conta abaixo.
Chave SSH a sua Sem senha: o cloud-init desliga PasswordAuthentication.
IP público sim O git.souzapablo.dev.br aponta para ele.
User data cloud-init.yaml Docker, 4 GB de swap e poda semanal de imagem.

A conta

Aqui ficam Forgejo (~300 MB) e Caddy (~20 MB). Quem comeria RAM é o job de CI: build do Nuxt mais Postgres de testcontainers, uns 3 GB, por poucos minutos a cada push. É exatamente por isso que o runner está em outro repositório e em outra máquina — e é o que permite esta VM ser a menor da tabela.

Tipo Config Preço O que quebra
BV1-1-10 1 / 1 GB / 10 GB R$ 34,99 O Forgejo pede 2 GB. Com 1 GB ele roda até um git gc ou uma indexação de código coincidirem, e aí cai sem explicar.
BV1-2-10 1 / 2 GB / 10 GB R$ 44,99 SO mais imagens Docker já passam de 5 GB. Disco cheio derruba o Forgejo, e SQLite cheio derruba com estrago.
BV1-2-40 1 / 2 GB / 40 GB R$ 69,99 Nada, com o runner fora daqui. É o padrão.
BV2-4-40 2 / 4 GB / 40 GB R$ 102,99 O passo para quando o runner mudar para a nuvem.
BV4-8-100 4 / 8 GB / 100 GB R$ 219,99 Nada aqui pede isso.

Os dois primeiros economizam R$ 25 e R$ 35 por mês para comprar falha intermitente — o pior tipo de economia, porque o custo aparece como tarde perdida, não como fatura.

Security group da instância:

Porta Origem Para quê
22 só o seu IP Administração.
80 0.0.0.0/0 Desafio HTTP-01 do ACME. Sem ela não há certificado.
443 0.0.0.0/0 (TCP e UDP) HTTPS e HTTP/3.
2222 só o seu IP SSH do git.

A regra de 2222 é por IPv4. Se a sua saída for IPv6 — e num roteador doméstico ela muda sozinha — o git push por SSH para de funcionar sem aviso e o sintoma é tempo esgotado, não recusa. Ou libera o IPv6 também, ou empurra por HTTPS.

Esta VM é a exceção documentada: ela é criada por clique, e só ela.

2. O DNS, no registro.br

A forge mora em souzapablo.dev.br, e não num domínio de produto, por dois motivos. Ela é infraestrutura sua, não um ativo de um produto: vai hospedar este e o que mais vier, e não deve mudar de endereço se o produto mudar de nome. E dev.br é sufixo público, então souzapablo.dev.br e posseiro.com.br são domínios registráveis distintos, com potes de cookie separados — a forge renderiza conteúdo que ela não controla (README, anexo de issue, arquivo raw), e fora do escopo do cookie nada disso alcança a sessão dos apps.

Painel → souzapablo.dev.br → aba DNSEditar zona. Um registro só:

git  3600 IN A  <IP-DA-VM>

Não mexa no apex. Ele serve o site pessoal no GitHub Pages e ainda não tem onde pousar — migra para esta mesma VM depois, no mesmo Caddy.

Publicar e conferir, do seu computador:

dig +short git.souzapablo.dev.br      # o IP da VM

Só siga quando ele devolver o IP. O Caddy pede certificado na primeira subida e a CA tem limite de tentativas por hora.

3. Subir

tar -czf - . | ssh ubuntu@<IP-DA-VM> 'tar -C /opt/forge -xzf -'
ssh ubuntu@<IP-DA-VM>
cd /opt/forge
cp .env.example .env && $EDITOR .env      # GIT_HOST e ACME_EMAIL
docker compose up -d
docker compose logs -f caddy              # o certificado sai em segundos

O envio é tar e não scp de propósito. O cloud-init já cria /opt/forge, e scp -r . destino com o destino existente copia o diretório para dentro dele. Trocar por scp * é pior: .env.example é dotfile, o glob não pega, e é justamente o arquivo da linha seguinte.

Abrir https://git.souzapablo.dev.br e criar a conta — a primeira vira admin. O cadastro fecha depois dela, pelo DISABLE_REGISTRATION.

4. O runner

O registro é feito daqui, mas o runner roda noutro lugar. Gere o segredo na VM:

SECRET=$(openssl rand -hex 20)
docker compose exec -u git forgejo \
  forgejo forgejo-cli actions register --name local --scope me --secret "$SECRET"

Guarde o UUID que ele imprime e o $SECRET que você gerou — não é um par que se recupera. O resto está no README de me/runner.

O que este repositório não cobre

Backup. Tudo aqui recria a forge vazia. O que tem valor está em dois volumes Docker que não estão e não devem estar no git:

Volume Conteúdo Se perder
forgejo-data SQLite, repositórios, issues, anexos Perdeu tudo. Não há de onde tirar.
caddy-data Chave da conta ACME e certificados Recuperável, mas a CA limita emissão por hora.

Enquanto o Object Storage do passo 3 não existe, uma cópia semanal para fora da VM já muda a natureza do problema:

docker compose stop forgejo
docker run --rm -v forgejo-data:/d -v "$PWD":/b alpine \
  tar -C /d -czf /b/forgejo-$(date +%F).tar.gz .
docker compose start forgejo

O stop não é zelo: SQLite copiado quente pode vir num estado que só se descobre inconsistente na hora de restaurar, que é a pior hora.

A CA. O documento de arquitetura escolhe a Buypass para fugir da Let's Encrypt, que é americana. A Buypass encerrou a emissão de TLS em 15/10/2025 e desligou o ACME em 15/04/2026 — a escolha não existe mais. Este Caddyfile usa o padrão do Caddy (Let's Encrypt, com ZeroSSL de reserva) para não travar o arranque. A substituta a avaliar é a Actalis (Itália), que tem ACME gratuito e exige EAB — o Caddy configura os dois com acme_ca e acme_eab. Decisão para antes do passo 6: o certificado da forge não é o certificado do produto.