@devix-tecnologia/deploy-kit
v0.1.0
Published
Kit de deploy Devix: tags, registry, git, env, docker, receita one-click, CapRover e release, dirigidos por um DevixConfig
Readme

@devix-tecnologia/deploy-kit
Kit de deploy da Devix: o que todo projeto faz para publicar uma imagem
Docker e apontar uma app do CapRover para ela — tags, registry, guard-rails de
git, .env por ambiente, build, receita One-Click, deploy e release.
Extraído do packages/deploy/scripts/lib do projeto de referência (decisão 3 do
ADR 001) e parametrizado: tudo
que antes era constante hardcoded — prefixo de imagem, mapa de nomes fora
do padrão, lista de imagens de terceiros, registro de serviços deployáveis,
prefixos CAPROVER_<PREFIX>_* — virou DevixConfig. Nada específico de
projeto mora aqui.
O kit não é um CLI. Ele é a biblioteca que o @devix-tecnologia/devix-cli
chama; é também o que um script solto pode importar por subpath.
Instalação e uso
import { planejarReleaseStaging, executarPassos } from '@devix-tecnologia/deploy-kit';
import { config } from './devix.config.js';
await executarPassos(
planejarReleaseStaging(config, { gerarReceita: true, pinSha: true }),
console.log
);Cada módulo também é importável isoladamente, para um comando pequeno não arrastar o kit inteiro:
import { comporTags } from '@devix-tecnologia/deploy-kit/tags';
import { consultarDigest } from '@devix-tecnologia/deploy-kit/docker';Módulos
| Subpath | O que faz | Principais exports |
| --- | --- | --- |
| /config | O DevixConfig e as consultas sobre ele. Valida com zod antes de qualquer build. | definirConfig, validarConfig, servicoDaConfig, servicosNoCicloDeTags, lerVersaoDoProjeto, diretorioDeArtefatos |
| /registry | Resolve o registry por imagem e compõe a referência (sem esquema). | resolverRegistry, comporReferencia, nomeImagemDoServico, variavelDeOverride |
| /tags | Esquema de tags, arquivos build-tags-*.json e decisão de push. | comporTags, tagsDoAmbiente, conteudoBuildTags, lerBuildTags, carregarBuildTags, decidirServicosParaPush |
| /git | Guard-rails: árvore limpa e HEAD pushado antes de publicar. | shaParaPublicacao, estadoDoPush, exigirHeadPushado |
| /env | Carrega .env.<ambiente> e valida contra a config (decisão 13). | carregarEnvDoAmbiente, validarEnv, esquemaEnv, credenciaisDoRegistry |
| /docker | Build, tag, pull, push, login e digest — tudo por um executor injetável. | buildImagem, tagImagem, pushTags, puxarImagem, loginNoRegistry, consultarDigest, imagemExisteNoRegistry, detectarContextoDocker |
| /one-click | Reescreve os defaultValue de imagem do template CapRover. Puro. | gerarReceita, resolverImagensDoTemplate, escolherTag, servicoDaVariavel |
| /caprover | Deploy por imagem e promoção staging → produção (decisão 11). | deployPorImagem, resolverImagemDeDeploy, planejarPromocao, promoverParaProducao |
| /release | Orquestra tudo como PassoDeRelease[], com colaboradores injetados. | planejarReleaseStaging, planejarReleaseProducao, executarPassos, registrarLog |
DevixConfig
Vive em devix.config.ts na raiz do projeto. Exemplo completo de um projeto
fictício (acme) — é a mesma config que os testes do kit usam:
import { definirConfig } from '@devix-tecnologia/deploy-kit/config';
export default definirConfig({
// Slug do projeto. Prefixo padrão das imagens: `acme-<serviço>`.
projeto: 'acme',
// Opcional. Nome da variável com a URL do registry (COM esquema).
// Padrão: DOCKER_REGISTRY_URL.
registry: { urlEnv: 'DOCKER_REGISTRY_URL' },
ambientes: {
staging: {
envFile: 'packages/deploy/.env.staging',
caprover: { urlEnv: 'CAPROVER_URL' },
},
production: {
envFile: 'packages/deploy/.env.production',
caprover: { urlEnv: 'CAPROVER_URL' },
},
},
servicos: {
// Serviço comum: imagem `acme-directus`, tag com versão.
directus: {
dockerfile: 'docker/directus/Dockerfile',
contexto: '.',
args: { NODE_VERSION: '24' },
caprover: { envPrefix: 'DIRECTUS' },
},
// `usaVersao: false` → só `<env>-latest` e `<sha>`. Para imagens de
// infraestrutura, cuja versão não acompanha a do artefato.
nginx: {
dockerfile: 'docker/nginx/Dockerfile',
contexto: 'docker/nginx',
usaVersao: false,
caprover: { envPrefix: 'NGINX' },
},
// `imagem` quebra o padrão `<projeto>-<serviço>` quando o nome no registry
// já existe com outro nome.
'mqtt-bridge': {
imagem: 'acme-activemq-mqtt-bridge',
dockerfile: 'docker/bridge/Dockerfile',
contexto: 'docker/bridge',
caprover: { envPrefix: 'MQTT_BRIDGE' },
},
// `foraDoCicloDeTags`: imagem própria com esquema de tag PRÓPRIO (por
// cliente, por região, versão upstream fixada). A receita mantém o
// defaultValue do template e o `--pin-sha` não exige build-tags dela.
portal: {
dockerfile: 'docker/portal/Dockerfile',
contexto: 'docker/portal',
foraDoCicloDeTags: true,
caprover: { envPrefix: 'PORTAL' },
},
// Terceiro: nunca construído nem publicado. A referência vem do template
// da receita; um override `DOCKER_REGISTRY_URL_LOKI` o espelha.
loki: { terceiro: true },
},
receita: {
template: 'packages/deploy/caprover/one-click-app.yml',
// Variável do template → serviço da config, quando os nomes divergem:
// `$$cap_bridge_image` publica como `mqtt-bridge`.
aliases: { bridge: 'mqtt-bridge' },
// Apps CapRover fora da receita. Consumido pela auditoria do
// `caprover-stack`, não pelo kit.
appsStandalone: ['acme-backup'],
},
// Origem da versão do artefato (decisão 12).
// Padrão: packages/deploy/package.json.
versao: { fonte: 'packages/deploy/package.json' },
// Onde caem os arquivos que o release GERA (decisão 14).
// Padrão: a pasta de `versao.fonte` — aqui, `packages/deploy`.
artefatos: { diretorio: 'packages/deploy' },
});Onde caem os artefatos do release
Três arquivos são gerados por um release, e todos os três vão para o diretório de artefatos (decisão 14 do ADR 001):
| Arquivo | Para quê |
| --- | --- |
| build-tags-<ambiente>-<serviço>.json | prova do que foi publicado; é o que autoriza a promoção para produção |
| one-click-app-<ambiente>.gerado.yml | receita com os defaultValue de imagem resolvidos |
| .releases/<data>-<ambiente>-<sha>.log | o que exatamente aconteceu naquele release |
O padrão é a pasta de versao.fonte (packages/deploy), não a raiz: a
config já aponta para lá duas vezes — ambientes.*.envFile e versao.fonte —
e deixá-los na raiz espalha um arquivo por serviço por ambiente ao lado do
package.json do projeto. artefatos.diretorio sobrepõe.
A distinção que importa: a raiz é a âncora do que a config declara
(receita.template, dockerfile, contexto); o diretório de artefatos é o
destino do que o release produz. Os primeiros continuam relativos à raiz.
Na leitura dos build-tags, o diretório de artefatos vem primeiro e a raiz
entra como segunda opção — um projeto com arquivos gravados antes desta
mudança continua promovendo sem rebuild.
Variáveis de ambiente que o kit lê
| Variável | Quem usa | Obrigatória |
| --- | --- | --- |
| DOCKER_REGISTRY_URL (ou o registry.urlEnv) | registry, docker | sim, no build |
| DOCKER_REGISTRY_URL_<IMAGEM> | registry | não (override por imagem) |
| DOCKER_REGISTRY | registry | não (legado, sem esquema, avisa) |
| DOCKER_REGISTRY_USERNAME / _PASSWORD | docker (login) | não (registry anônimo é válido) |
| DOCKER_USERNAME / DOCKER_PASSWORD | docker (login) | não (fallback) |
| DOCKER_HOST, DOCKER_BUILDKIT | docker | não (respeitadas se presentes) |
| CAPROVER_URL (ou o caprover.urlEnv do ambiente) | caprover | sim, no deploy |
| CAPROVER_<PREFIX>_APP_NAME / _APP_TOKEN | caprover | sim, por serviço deployado |
| CAPROVER_<PREFIX>_URL | caprover | não (sobrepõe a URL do ambiente) |
| CAPROVER_<PREFIX>_IMAGE | caprover | não (escape hatch: imagem literal) |
Precedência de resolução do registry
DOCKER_REGISTRY_URL_<IMAGEM> → override por imagem (espelho, registry do cliente)
DOCKER_REGISTRY_URL → padrão do projeto, COM esquema
DOCKER_REGISTRY → legado, SEM esquema; resolve, mas avisa
(nenhuma) → RegistryNaoConfiguradoErrorO esquema fica na configuração, não na tag: é ele que permite ao
docker login acertar o endpoint e distinguir http de https. Uma tag Docker
não aceita esquema, então resolverRegistry devolve os dois lados —
url (com) e host (sem) —, e comporReferencia usa o host.
Dois casos especiais:
- Docker Hub (
docker.io,registry-1.docker.io,index.docker.io) resolvehost: '': a referência sai sem prefixo, como o Docker espera. - Terceiro sem override não resolve registry nenhum: a referência é a que
está fixada no template da receita. Com override, o terceiro é espelhado
preservando o caminho original (
grafana/loki→<registry>/grafana/loki).
Esquema de tags
Todas as tags saem sem esquema. Todo build publica três (ou duas):
| Tag | Para quê |
| --- | --- |
| <imagem>:<ambiente>-latest | flutuante do ambiente; é reescrita a cada push |
| <imagem>:<tag de versão> | rollback por versão (omitida com usaVersao: false) |
| <imagem>:<sha> | identidade imutável do artefato; sempre publicada |
A tag de versão difere por ambiente (decisão 12):
| Ambiente | Tag de versão | Por quê |
| --- | --- | --- |
| production | production-<versão> | a versão bumpa na promoção (lerna version, conventional commits), então ela identifica um artefato só |
| staging | staging-<versão>-<sha> | há muitos builds por versão; sem o sha, o segundo build sobrescreveria o primeiro e staging-1.2.0 não identificaria nada |
A versão sai do version do package.json apontado por versao.fonte
(padrão packages/deploy/package.json).
Produção é promoção, não rebuild (decisão 11): promoverParaProducao puxa
<imagem>:<sha> publicada por staging, retagueia para as tags de produção e
publica. Ela recusa um sha que não consta nos build-tags-staging-*.json,
salvo force — e o log do release registra que passou sem prova.
Como o --platform é decidido
O alvo é sempre o CapRover, que roda Linux x86_64. Buildar num Mac Apple Silicon sem forçar a plataforma produz uma imagem arm64 que sobe e morre com "exec format error" — falha no deploy, longe da causa.
plataforma: null → nenhum --platform (desligado de propósito)
plataforma: 'linux/arm64' → o valor informado
process.arch !== 'x64' → --platform linux/amd64
process.arch === 'x64' → nenhum --platform (build nativo, mais rápido)DOCKER_HOST e o contexto Docker ativo são herdados, nunca sobrescritos:
buildar numa máquina Linux a partir do Mac é só exportar
DOCKER_HOST (e aí process.arch do host que decide continua sendo o do Mac —
forçar linux/amd64 num daemon amd64 é inócuo).
A única variável que o kit decide sozinho é DOCKER_BUILDKIT, herdando o
wrapper de Colima de origem:
DOCKER_BUILDKIT definido → respeitado
contexto/DOCKER_HOST com "colima", ou `colima status` ok no macOS → '0'
caso contrário → '1'BuildKit desligado em Colima porque o buildkitd da VM quebra em builds multi-estágio grandes; o builder clássico não.
Testes
299 testes, vitest --run. Nenhum toca docker, rede, git ou filesystem
real: o executor de processo (ExecutorDeProcesso), o git
(ExecutarGit), o leitor e o escritor de arquivo e o cliente CapRover
(ClienteCapRover) são todos injetados, e nos testes são sempre falsos.
A config de exemplo dos testes é src/teste/projeto-ficticio.ts — o mesmo
acme deste README. Ela fica fora do pacote publicado.
O que ficou de fora, e por quê
- Auditorias da receita (
$cap_com um$,srv-captain--órfão,depends_onfora de ordem, heredoc emdockerfileLines, paridade staging/produção, pgcat sem réplica, superset sub-filter…). Vão para o pacotecaprover-stack, junto com o template que elas auditam (decisão 16). O kit resolve imagens; ele não opina sobre o conteúdo da receita. - O cliente CapRover.
@devix-tecnologia/caprover-clientNÃO é dependência de compilação:caprover.types.tsdeclara só o método que o kit usa (deployarImagem). Assim o kit não fica preso ao contrato 1.14/1.15 do painel e os testes não precisam de servidor fake. - Início automático do daemon Docker (
colima start,open -a Docker, o laço de espera dodocker-build-wrapper.mjs). É preparo de máquina, com espera interativa; o kit expõedaemonRespondendo()e quem decide o que fazer com um "não" é o CLI. A parte que importava — detecção de Colima e escolha do BuildKit — foi absorvida emdetectarContextoDocker. docker compose. A versão anterior buildava viadocker-compose -f … build <serviço>, o que amarrava a lista de imagens ao compose e exigia um mapa de "nome do serviço no compose". O kit chamadocker builddireto, comdockerfileecontextovindos da config (decisão 9: um Dockerfile por imagem).- Impressão colorida /
chalk/zx. O kit devolve dados e passos nomeados; quem imprime é o CLI. É o que permite testar a orquestração sem capturar stdout. aplicar-receita-local,e2e-caprover, diagnósticos de conectividade. São ferramentas de teste de integração de um projeto, não parte do deploy.- Multi-tenant / multi-região (
--service portal:cliente-x). É domínio de um projeto (decisão 4). O equivalente genérico aqui éforaDoCicloDeTagsmais um serviço por slug na config do projeto. lerna versione a tag gitv<versão>. O bump de versão é do monorepo do projeto (decisão 12); o kit só LÊ a versão resultante.
