Pular para conteúdo

0011 — O container do app é artefato do repo, derivado de template

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-16 · Decidido em: 2026-07-16

Contexto

A plataforma nunca prescreveu Dockerfile. Não havia template, artefato rastreado, item de checklist nem receita por stack — e a constituição não o mencionava em § nenhum. Todo o conhecimento de container vivia no registro "não pise nesta mina": armadilhas boas e verificadas (.dockerignore × docs/app.json, .git/HEAD no build, ARG FLUTTER_VERSION × .fvmrc). O registro "isto é o que você constrói" nunca foi escrito. Coolify dizia "Build Pack: Dockerfile" tratando o arquivo como fato da natureza.

Havia contradição interna: a §5 exige health check obrigatório em todo app deployável, mas a Estrutura padrão de cada repo (§3) não listava o artefato onde o HEALTHCHECK vive. Prescrevia o parafuso, não a parede.

E era assimétrico por stack: flutter.md tinha a ponte jq--dart-define; java-micronaut.md não citava Dockerfile em linha nenhuma — a stack que mais deploya no Coolify era a sem receita.

O caso concreto (loop §6, 2026-07-16): o thoms/int-produtos-ecom nasceu doc-only, virou código, passou nos testes e no e2e local — e falhou no deploy do Coolify por não ter Dockerfile. A cadeia: central não prescreve → o /x-planejar não tinha o que puxar → a T0.1 do plano pediu build.gradle + /health + app.json, sem container → a T7.1 escopou deploy como "operação humana (grant + env Coolify)" → o ci.yml roda só ./gradlew check, não builda imagemo Coolify foi o primeiro a tocar o Dockerfile, em produção. A recuperação foi copiar do irmão: o arquivo criado carrega literalmente o comentário "Espelha o Dockerfile do integrador".

O integrador tem Dockerfile porque o plano dele tinha uma fase de deploy escrita à mão, não porque o padrão mandou. Conhecimento vivo num repo, ausente no central, irmão quebrado em produção — o mesmo formato do drift de cache de Flutter Web (backlog #84).

Decisão

App deployável leva Dockerfile e .dockerignore no repo desde o primeiro commit de código, derivados de template rastreado, e o /xadm-release os exercita antes da tag.

  • Piso na constituição §3 (Estrutura padrão): os dois arquivos entram na árvore, com a razão — o Coolify builda o Dockerfile do repo, não há buildpack, e o ci.yml não builda imagem. Fecha a contradição com o health check obrigatório da §5.
  • Templates rastreados e opcionais: dockerfile-java e dockerignore-java entram no manifesto (sha256, sincronizados pela /xadm-docs), opcionais como o kit de UI — biblioteca, CLI distribuído como artefato e app Flutter não os têm, e ausência aí não é defasagem.
  • Gate no pré-flight do /xadm-release: docker build -t <slug>:pre . antes da tag. Sem daemon Docker → degradado (conferir que os arquivos existem) e declarado no relatório final, no mesmo padrão do gate de código degradado (#71) e da baseline inalcançável (#53) — meio gate é decisão explícita, não silêncio.
  • Detalhe operável na Engenharia: Java/Micronaut §Deploy (multi-stage, build-only, nome do jar fixo, Jammy × Alpine, flags de heap). Um dono por fato (§1.9); a constituição fica com o piso.

Alternativas consideradas

  • Só receita em prosa na engenharia, sem template rastreado: cada app copiaria do doc à mão. Preterido — é exatamente o xerox-do-irmão que gerou este feedback; sem artefato rastreado a /xadm-docs não tem o que cobrar.
  • docker build como job do ci.yml, em todo push: pega mais cedo que o release, mas custa minutos por push e exige dind no runner. Preterido por proporcionalidade — o pré-flight pega antes da tag, que é antes do deploy, que é onde dói.
  • Só item de conformidade no /xadm-docs (checar existência): barato e pegaria este caso, mas não pega Dockerfile quebrado, só ausente. Preterido em favor do build real; o item de conformidade pode voltar como camada extra se o gate provar insuficiente.
  • Templatizar Flutter na mesma leva: preterido por ora — o Flutter já tem a ponte jq documentada, e o backlog #84 decidiu explicitamente não templatizar o map de nginx até a receita rodar em mais de um app. Reabrir sem dado novo seria contradizer a decisão anterior. Só Java agora, que estava vazio.

Consequências

  • App novo nasce deployável. O /x-planejar tem o que puxar para a fase de bootstrap; a falha do ecom não se repete por omissão.
  • O Dockerfile deixa de ser o único artefato do repo que nenhum CI toca.
  • Um bug latente em produção achado ao dogfoodar o template. Buildar o template contra um app real (em vez de publicá-lo por inspeção) expôs que o --mount=type=cache,target=/root/.gradle que os dois apps Java da casa já usam tem id default = o target e sharing default = shared — ou seja, todos os apps Java dividem um cache mount no servidor do Coolify, e o Gradle não tolera acesso concorrente. Dois deploys ao mesmo tempo, ou um build morto que deixou lock stale, falham com "Timeout waiting to lock journal cache … It is currently in use by another process" — intermitente, com cara de bug do app. O template usa id=gradle-<slug> + sharing=locked (o que a doc do Docker prescreve para o apt, pela mesma razão). Migração oportunista nos apps existentes.
  • Semântica do .dockerignore documentada e verificada, não inferida: o Docker casa padrões com filepath.Match do Go (* não cruza /), não com formato .gitignore. Consequências que os repos atuais carregam sem saber: build bare exclui só a raiz (sub/build/ viaja pro daemon — latente até alguém modularizar), e **/*.jar quebraria o build ao excluir o gradle/wrapper/gradle-wrapper.jar. O template usa **/build e mantém *.jar raiz-only de propósito, com o porquê escrito.
  • Migração oportunista: os apps com Dockerfile à mão (integrador, int-produtos-ecom) divergem do template no primeiro /xadm-docs. Reconciliar quando tocarem o container — o template é o destino, não um retrofit urgente.
  • Sem guarda offline (como 0009/0010): o valida-frontmatter não valida Dockerfile. O enforcement é o gate do release + revisão de PR.