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 |
||
|---|---|---|
| .env.example | ||
| .gitignore | ||
| Caddyfile | ||
| cloud-init.yaml | ||
| docker-compose.yml | ||
| README.md | ||
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 fixabr-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 primeirotofu 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 DNS → Editar 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.