Second Brain

For future Claude

Pipeline de release D2U.NET: Util/D2U.NET/deploy_pipeline.py + README Util/D2U.NET/DEPLOY-PIPELINE.md. Build na máquina Windows, deploy por SSH para 192.168.0.29. A unidade atómica é a tag da imagem Docker, não uma pasta — d2unet-web:<versão> imutável, :local é só apontador; a troca é docker tag + docker compose up -d --no-build. O deploy.py legado continua a existir e a funcionar (extrai por cima, sem backup nem rollback). Não há repositório git em nenhuma das árvores, logo o argumento branch/tag é um rótulo de versão. Duas releases reais e ciclo de rollback completo testados a 2026-09-03; o servidor corre main-20260903T213703Z. Hub: Homologacao · Observability · Testes.


CI/CD — pipeline de release D2U.NET

Levantamento (2026-09-03)

Pergunta Resposta
Solução d2u.net.slnx (SLNX), 12 projetos; dois artefactos publicáveis: D2U.Web e D2U.Workers
Entry point src/D2U.Web (Microsoft.NET.Sdk.Web), net10.0
Hospedagem Docker Compose, projeto d2unet — nem IIS, nem systemd, nem nginx
Kestrel dentro do container na 8080, publicada no host como 8098, sem proxy à frente
Utilizador container corre como root (uid=0)
Diretório publicado /app dentro da imagem; o publish/ no host é só contexto de build
Deploy actual deploy/deploy.py — tar por SFTP, extrai por cima, docker rm -f, up -d --build
Config no repo appsettings.json, appsettings.Production.json (placeholders ${MYSQL_HOST})
Config no servidor .env, 68 chaves, todos os segredos de pagamento e mail
Logs Serilog só consola → Docker json-file 20 MB × 5 → docker logs; sem sudo no servidor
Versionamento nenhumDirectory.Build.props não define Version, tudo reporta 1.0.0
Git não existe repo em C:\Projetos\D2U.NET nem em C:\Projetos\Util\D2U.NET
CI existente .github/workflows/ci.yml (build + test com Postgres), sem job de deploy, nunca corre
Disco 313 GB livres de 458 GB; imagens 16,69 GB; build cache 69,31 GB, 45,35 GB recuperáveis

Porque a tag da imagem é a unidade atómica

docker/Dockerfile.web faz COPY publish/web/ . sobre uma base aspnet:10.0 limpa. Logo /app dentro da imagem é o único caminho de carregamento e não pode acumular DLLs órfãs entre deploys, ao contrário de copiar por cima de um diretório. Trocar d2unet-web:local para apontar a uma tag de versão nova e correr up -d --no-build é uma troca atómica real, e o --no-build elimina a armadilha de 2026-08-19 (o COPY publish/web/ marcado CACHED a devolver SUCCESS com binários antigos — ver Defeito-DBNull-Bool-2026-08-19).

O que o pipeline garante

Nove requisitos, todos implementados: aborta no primeiro erro (a troca é o penúltimo passo, logo uma falha deixa a aplicação intocada); valida o artefacto por bandas de tamanho, parse de deps.json/runtimeconfig.json/appsettings.Production.json, contagem de ficheiros e sha256; publica em dist/<versão> sempre recriado e releases/<versão> sempre novo; confirma fonte única de verdade com find / -xdev mais sha256sum dentro do container contra o artefacto validado; declara a posse de cada ficheiro de config; faz backup do .env e poda para as últimas --keep (3) releases e imagens, com gate de espaço livre; health check HTTP real exigindo 200 e "status":"healthy"; rollback num comando; e diagnóstico no README.

Bandas de tamanho são por serviço: D2U.Workers.dll tem 58 KB porque o projeto de workers é um host fino — a lógica vive em D2U.Infrastructure.dll. Uma banda global rejeitava o artefacto correcto (aconteceu no primeiro dry-run).

Mudanças de comportamento face ao deploy legado

Tema Legado Pipeline novo
.env do servidor sobrescrito a cada deploy pela cópia da máquina de build nunca escrito; só validado (5 chaves obrigatórias)
Workers__Enabled depende de alguém passar --workers-off propriedade do servidor; muda-se com deploy/set_workers.py
appsettings.Development*/Testing publicados dentro da imagem de produção excluídos do artefacto
Porta do health check assumida lida de DOCKER_WEB_PORT no .env do servidor
Rollback inexistente python deploy_pipeline.py --rollback

Antes de mudar a posse do .env confirmei que a divergência real era de uma chave (Workers__Enabled, local true / servidor false), sem divergência de segredos.

Dívida técnica registada (decisão: publicar e stage primeiro)

  • Segredos em texto simples. Stripe, PayPal, RealEx, GoogleCheckout, SMTP, IMAP, MS Graph e Lexacom vivem no .env do servidor e numa cópia não versionada em deploy/.env na máquina de build. Decisão explícita de 2026-09-03: publicar e fazer stage primeiro, resolver depois. Candidatos: Docker secrets, ou cofre. Liga a Seguranca (Onda 9, "Secrets: Key Vault / env").
  • Credenciais SSH em código. deploy/_config.py tem host, utilizador e password como valores por omissão. Sobreponíveis por variáveis de ambiente, mas o default está no ficheiro.
  • Sem versionamento de assembly. Sem Version no Directory.Build.props, todos os builds reportam 1.0.0 e o /health não expõe versão; a identidade de uma release é carregada pelo sha256. Estampar a versão precisa de aprovação (é build da aplicação) — uma linha no Directory.Build.props mais -p:Version= no publish, e o --expect-version passa a ter dentes.
  • Sem git. Sem repositório não há checkout, não há histórico de commits no manifesto, e o ci.yml nunca corre. O manifesto já grava "vcs": "none" e passa a gravar HEAD mais os últimos 50 commits assim que existir repo, sem alterações ao script.

Limpezas pendentes no servidor (não autorizadas)

Nenhuma foi executada. A poda do pipeline toca apenas em caminhos por baixo de releases/.

Item Tamanho Nota
/home/edinaldo/projetos/D2U.NET/app/ 92 MB cópia da árvore de código de 22 Jul, com .env e compose próprios; sem DLLs da aplicação (verificado)
publish/ na raiz remota 315 MB contexto de build do deploy legado; obsoleto quando releases/ estiver em uso
Build cache do Docker 45,35 GB recuperáveis o verdadeiro risco de disco, muito acima dos backups

Fora de âmbito

Zero-downtime real (o up -d recria os containers, ~5–10 s; blue/green exigia um proxy que não existe), migrações de base de dados (AutoMigrate=false, MySQL partilhado com o Java), container sem ser root, job de deploy no GitHub Actions, e a suíte de testes completa como gate — esta tem ~40 falhas pré-existentes (Testes), por isso o gate por omissão é --filter Parity2026 (216 testes verdes no dry-run de 2026-09-03), a prática já usada no test_and_deploy.py.

O que as releases reais ensinaram (2026-09-03)

Três falhas apareceram só por correr a sério, e todas geraram correcções no pipeline.

1. O disco da máquina de build encheu. Verificava espaço no servidor mas não localmente, e o dotnet publish reportou "not enough space on the disk" como warning de retry — continuou a andar e teria produzido um artefacto incompleto. Havia 210 MB livres em 232 GB usados. Correcções: gate de 600 MB no preflight local, tarball enviado a partir de memória em vez de escrito em disco, e --keep-local a 0 por omissão (o artefacto já está no servidor com o manifesto). Libertados 2,3 GB de bin/obj mais 305 MB de deploy/publish/, com autorização.

2. MCR devolveu 429 Too Many Requests. O docker build resolve os metadados da base em mcr.microsoft.com em cada invocação; a imagem web construiu e a dos workers apanhou o rate limit. Correcção: retry com backoff (4 tentativas) para padrões transitórios de registry/rede; erros não transitórios continuam a abortar na primeira.

3. Presumi mal que as imagens eram idênticas. docker images mostrava o mesmo CreatedAt e tamanho para as três tags, o que me levou a duvidar que o rollback provasse algo. Os image IDs são distintos (5fe2aa25, 69731c8d, 43fdf039) — o timestamp da coluna não distingue builds.

As duas primeiras falharam antes de qualquer coisa tocar no servidor (passos 3 e 7, a troca é o passo 8), com releases/ intacto e a aplicação no ar inalterada. É o requisito 1 a funcionar.

Evidência

Passo Resultado
Testes gate 216 Parity2026 verdes (198 unit + 18 integration)
Artefacto D2U.Web.dll 1,8 MB sha256=7fc288e0… · D2U.Workers.dll 58 KB sha256=2a394990…
Pacote 125,5 MB, streamed de memória
Fonte única de verdade exactamente um DLL em /app por container, sha256 igual ao artefacto validado
Health 200 healthy em cada release e em cada rollback
Rollback 213703Z213121Z213703Z; container passou a sha256:69731c8d… e voltou a 43fdf039…

O alvo de rollback da primeira release foi criado etiquetando as imagens de 2026-08-20 como pre-pipeline-20260820 — aditivo, sem remover nada.

Próximo passo

Nada bloqueante. Quando houver git, o manifesto começa a gravar HEAD e commits sem alterações ao script. A release parcial main-20260903T212245Z (só imagem web, da falha do MCR) é inerte e sai na próxima poda.