nexus-core-v3 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/agents/README.md +133 -0
  4. package/agents/_protocol.md +107 -0
  5. package/agents/analyst.md +138 -0
  6. package/agents/architect.md +146 -0
  7. package/agents/data-engineer.md +170 -0
  8. package/agents/dev.md +134 -0
  9. package/agents/devops.md +141 -0
  10. package/agents/nexus-master.md +147 -0
  11. package/agents/pm.md +133 -0
  12. package/agents/po.md +138 -0
  13. package/agents/qa.md +192 -0
  14. package/agents/sm.md +122 -0
  15. package/agents/squad-creator.md +121 -0
  16. package/agents/ux-design-expert.md +165 -0
  17. package/artifact-manifest.json +903 -0
  18. package/bin/nexus.mjs +37 -0
  19. package/checklists/README.md +49 -0
  20. package/checklists/architect-checklist.md +47 -0
  21. package/checklists/change-checklist.md +61 -0
  22. package/checklists/db-predeploy-checklist.md +57 -0
  23. package/checklists/design-quality-checklist.md +57 -0
  24. package/checklists/discovery-checklist.md +36 -0
  25. package/checklists/foundation-checklist.md +39 -0
  26. package/checklists/launch-checklist.md +39 -0
  27. package/checklists/pm-checklist.md +48 -0
  28. package/checklists/po-master-checklist.md +64 -0
  29. package/checklists/reality-check-checklist.md +49 -0
  30. package/checklists/story-dod-checklist.md +52 -0
  31. package/checklists/story-draft-checklist.md +36 -0
  32. package/dist/bin/dashboard.html +279 -0
  33. package/dist/bin/nexus.mjs +20008 -0
  34. package/dist/constitution.yaml +76 -0
  35. package/knowledge/README.md +57 -0
  36. package/knowledge/architecture/architectural-styles-map.md +182 -0
  37. package/knowledge/architecture/design-patterns-gof.md +192 -0
  38. package/knowledge/architecture/distributed-patterns-cheatsheet.md +201 -0
  39. package/knowledge/architecture/saas-subscription-blueprint.md +355 -0
  40. package/knowledge/architecture/system-design-tradeoffs.md +231 -0
  41. package/knowledge/architecture/t3-fullstack-typesafe-stack.md +273 -0
  42. package/knowledge/copy/landing-copy-that-converts.md +168 -0
  43. package/knowledge/data/postgres-indexing-and-tuning.md +263 -0
  44. package/knowledge/data/schema-modeling-decisions.md +273 -0
  45. package/knowledge/data/supabase-rls-patterns.md +316 -0
  46. package/knowledge/data/zero-downtime-migrations.md +308 -0
  47. package/knowledge/devops/cicd-pipeline-best-practices.md +318 -0
  48. package/knowledge/devops/production-dockerfile.md +283 -0
  49. package/knowledge/devops/twelve-factor-app.md +398 -0
  50. package/knowledge/engineering/clean-code-principles.md +429 -0
  51. package/knowledge/engineering/effective-code-review.md +204 -0
  52. package/knowledge/engineering/testing-strategy-beyond-unit.md +307 -0
  53. package/knowledge/governance/risk-matrix.md +56 -0
  54. package/knowledge/integration/mcp-server-selection-matrix.md +235 -0
  55. package/knowledge/marketing/copy-que-converte.md +43 -0
  56. package/knowledge/marketing/funil-e-jornada.md +36 -0
  57. package/knowledge/negocios/proposta-vencedora.md +38 -0
  58. package/knowledge/negocios/roi-e-unit-economics.md +46 -0
  59. package/knowledge/pipeline/1-descobrir.md +26 -0
  60. package/knowledge/pipeline/2-estrategizar.md +26 -0
  61. package/knowledge/pipeline/3-estruturar.md +27 -0
  62. package/knowledge/pipeline/4-construir.md +27 -0
  63. package/knowledge/pipeline/5-endurecer.md +28 -0
  64. package/knowledge/pipeline/6-lancar.md +27 -0
  65. package/knowledge/pipeline/7-operar.md +27 -0
  66. package/knowledge/security/lgpd-conformidade-basica.md +35 -0
  67. package/knowledge/security/owasp-secure-coding-gates.md +220 -0
  68. package/knowledge/security/owasp-top10-threat-assessment.md +287 -0
  69. package/knowledge/security/threat-modeling-stride.md +34 -0
  70. package/knowledge/web-craft/a11y-audit-checklist.md +251 -0
  71. package/knowledge/web-craft/accessible-component-patterns.md +383 -0
  72. package/knowledge/web-craft/anti-ai-look.md +114 -0
  73. package/knowledge/web-craft/design-system-from-code.md +195 -0
  74. package/knowledge/web-craft/intrinsic-css-layout.md +420 -0
  75. package/knowledge/web-craft/style-cloning.md +185 -0
  76. package/knowledge/web-craft/visual-polish-review.md +183 -0
  77. package/package.json +55 -0
  78. package/runbooks/campanha-de-conteudo.md +36 -0
  79. package/runbooks/feature-em-projeto-existente.md +37 -0
  80. package/runbooks/mvp-startup.md +38 -0
  81. package/runbooks/resposta-a-incidente.md +37 -0
  82. package/squads/exemplo-conteudo/agents/editor-chefe.md +48 -0
  83. package/squads/exemplo-conteudo/agents/pesquisador.md +44 -0
  84. package/squads/exemplo-conteudo/agents/redator.md +45 -0
  85. package/squads/exemplo-conteudo/knowledge/estilo-editorial.md +21 -0
  86. package/squads/exemplo-conteudo/squad.yaml +19 -0
  87. package/squads/exemplo-conteudo/tasks/pesquisar-fontes.md +26 -0
  88. package/squads/exemplo-conteudo/tasks/planejar-pauta.md +27 -0
  89. package/squads/exemplo-conteudo/tasks/redigir-artigo.md +26 -0
  90. package/squads/exemplo-conteudo/tasks/revisar-artigo.md +27 -0
  91. package/squads/marketing/agents/analista.md +56 -0
  92. package/squads/marketing/agents/chefe-marketing.md +65 -0
  93. package/squads/marketing/agents/conteudo.md +55 -0
  94. package/squads/marketing/agents/copy.md +55 -0
  95. package/squads/marketing/agents/growth.md +56 -0
  96. package/squads/marketing/agents/social.md +55 -0
  97. package/squads/marketing/squad.yaml +17 -0
  98. package/squads/marketing/tasks/aprovar-campanha.md +43 -0
  99. package/squads/negocios/agents/chefe-negocios.md +65 -0
  100. package/squads/negocios/agents/financas-roi.md +55 -0
  101. package/squads/negocios/agents/suporte.md +55 -0
  102. package/squads/negocios/agents/vendas-proposta.md +56 -0
  103. package/squads/negocios/squad.yaml +17 -0
  104. package/squads/negocios/tasks/aprovar-proposta.md +40 -0
  105. package/squads/security/agents/appsec-reviewer.md +59 -0
  106. package/squads/security/agents/chefe-seguranca.md +65 -0
  107. package/squads/security/agents/compliance-auditor.md +60 -0
  108. package/squads/security/agents/threat-modeler.md +60 -0
  109. package/squads/security/squad.yaml +20 -0
  110. package/squads/security/tasks/aprovar-gate-seguranca.md +42 -0
  111. package/squads/security/tasks/emitir-parecer-conformidade.md +42 -0
  112. package/tasks/README.md +72 -0
  113. package/tasks/accessibility-wcag-checklist.md +69 -0
  114. package/tasks/advanced-elicitation.md +42 -0
  115. package/tasks/analyze-performance.md +54 -0
  116. package/tasks/analyze-project-structure.md +59 -0
  117. package/tasks/apply-qa-fixes.md +57 -0
  118. package/tasks/architect-analyze-impact.md +62 -0
  119. package/tasks/archive-squad.md +52 -0
  120. package/tasks/audit-codebase.md +53 -0
  121. package/tasks/build-component.md +61 -0
  122. package/tasks/calculate-roi.md +63 -0
  123. package/tasks/ci-cd-configuration.md +51 -0
  124. package/tasks/collect-visual-evidence.md +62 -0
  125. package/tasks/compose-molecule.md +57 -0
  126. package/tasks/consolidate-patterns.md +54 -0
  127. package/tasks/create-brownfield-prd.md +54 -0
  128. package/tasks/create-competitor-analysis.md +42 -0
  129. package/tasks/create-deep-research-prompt.md +62 -0
  130. package/tasks/create-doc.md +62 -0
  131. package/tasks/create-epic.md +49 -0
  132. package/tasks/create-front-end-spec.md +56 -0
  133. package/tasks/create-migration-plan.md +57 -0
  134. package/tasks/create-next-story.md +66 -0
  135. package/tasks/create-prd.md +53 -0
  136. package/tasks/create-project-brief.md +47 -0
  137. package/tasks/create-rls-policies.md +59 -0
  138. package/tasks/create-schema.md +57 -0
  139. package/tasks/create-service.md +55 -0
  140. package/tasks/create-squad.md +100 -0
  141. package/tasks/create-suite.md +62 -0
  142. package/tasks/db-apply-migration.md +56 -0
  143. package/tasks/db-domain-modeling.md +57 -0
  144. package/tasks/db-dry-run.md +50 -0
  145. package/tasks/db-env-check.md +57 -0
  146. package/tasks/db-load-csv.md +54 -0
  147. package/tasks/db-policy-apply.md +58 -0
  148. package/tasks/db-rollback.md +51 -0
  149. package/tasks/db-run-sql.md +61 -0
  150. package/tasks/db-seed.md +52 -0
  151. package/tasks/db-smoke-test.md +51 -0
  152. package/tasks/db-snapshot.md +48 -0
  153. package/tasks/db-verify-order.md +49 -0
  154. package/tasks/deliberate.md +46 -0
  155. package/tasks/design-indexes.md +59 -0
  156. package/tasks/dev-develop-story.md +61 -0
  157. package/tasks/document-project.md +59 -0
  158. package/tasks/execute-checklist.md +57 -0
  159. package/tasks/execute-epic-plan.md +52 -0
  160. package/tasks/execute-subtask.md +51 -0
  161. package/tasks/extend-pattern.md +63 -0
  162. package/tasks/extend-squad.md +60 -0
  163. package/tasks/extract-patterns.md +64 -0
  164. package/tasks/extract-tokens.md +59 -0
  165. package/tasks/facilitate-brainstorming-session.md +42 -0
  166. package/tasks/generate-ai-frontend-prompt.md +57 -0
  167. package/tasks/generate-documentation.md +60 -0
  168. package/tasks/generate-migration-strategy.md +57 -0
  169. package/tasks/generate-shock-report.md +56 -0
  170. package/tasks/mcp-management.md +66 -0
  171. package/tasks/orchestrate.md +50 -0
  172. package/tasks/perform-market-research.md +42 -0
  173. package/tasks/plan-create-context.md +57 -0
  174. package/tasks/plan-create-implementation.md +58 -0
  175. package/tasks/po-close-story.md +60 -0
  176. package/tasks/po-manage-story-backlog.md +59 -0
  177. package/tasks/po-pull-story.md +60 -0
  178. package/tasks/po-sync-story.md +59 -0
  179. package/tasks/pr-automation.md +50 -0
  180. package/tasks/pre-push-quality-gate.md +54 -0
  181. package/tasks/push.md +53 -0
  182. package/tasks/qa-browser-console-check.md +52 -0
  183. package/tasks/qa-create-fix-request.md +58 -0
  184. package/tasks/qa-evidence-requirements.md +55 -0
  185. package/tasks/qa-false-positive-detection.md +55 -0
  186. package/tasks/qa-fix-issues.md +55 -0
  187. package/tasks/qa-gate.md +53 -0
  188. package/tasks/qa-migration-validation.md +58 -0
  189. package/tasks/qa-nfr-assess.md +45 -0
  190. package/tasks/qa-review-story.md +56 -0
  191. package/tasks/qa-risk-profile.md +45 -0
  192. package/tasks/qa-security-checklist.md +64 -0
  193. package/tasks/qa-test-design.md +47 -0
  194. package/tasks/qa-trace-requirements.md +48 -0
  195. package/tasks/release-management.md +53 -0
  196. package/tasks/repository-cleanup.md +61 -0
  197. package/tasks/route.md +44 -0
  198. package/tasks/run-tests.md +50 -0
  199. package/tasks/security-audit.md +54 -0
  200. package/tasks/setup-database.md +60 -0
  201. package/tasks/setup-design-system.md +60 -0
  202. package/tasks/shard-doc.md +60 -0
  203. package/tasks/spec-assess-complexity.md +55 -0
  204. package/tasks/spec-critique.md +64 -0
  205. package/tasks/spec-gather-requirements.md +48 -0
  206. package/tasks/spec-research-dependencies.md +42 -0
  207. package/tasks/spec-write-spec.md +50 -0
  208. package/tasks/test-as-user.md +52 -0
  209. package/tasks/ux-create-wireframe.md +54 -0
  210. package/tasks/ux-user-research.md +55 -0
  211. package/tasks/validate-next-story.md +61 -0
  212. package/tasks/validate-squad.md +55 -0
  213. package/tasks/verify-subtask.md +52 -0
  214. package/tasks/version-management.md +45 -0
  215. package/templates/README.md +47 -0
  216. package/templates/architecture-tmpl.md +115 -0
  217. package/templates/competitor-analysis-tmpl.md +87 -0
  218. package/templates/epic-tmpl.md +83 -0
  219. package/templates/front-end-spec-tmpl.md +110 -0
  220. package/templates/market-research-tmpl.md +98 -0
  221. package/templates/migration-plan-tmpl.md +92 -0
  222. package/templates/prd-tmpl.md +95 -0
  223. package/templates/project-brief-tmpl.md +100 -0
  224. package/templates/qa-verdict-tmpl.md +73 -0
  225. package/templates/rls-policies-tmpl.md +93 -0
  226. package/templates/schema-design-tmpl.md +107 -0
  227. package/templates/spec-tmpl.md +88 -0
  228. package/templates/squad/agent-dna-tmpl.md +72 -0
  229. package/templates/squad/chief-dna-tmpl.md +98 -0
  230. package/templates/squad/squad-task-tmpl.md +50 -0
  231. package/templates/squad/squad-yaml-tmpl.md +47 -0
  232. package/templates/story-tmpl.md +63 -0
@@ -0,0 +1,283 @@
1
+ ---
2
+ id: production-dockerfile
3
+ domain: devops
4
+ agents: [devops]
5
+ when: "ao escrever um Dockerfile destinado a produção"
6
+ ---
7
+
8
+ # Dockerfile de produção — imagens enxutas, seguras e cacheáveis
9
+
10
+ ## O problema
11
+
12
+ Um Dockerfile que "funciona" na sua máquina quase nunca é um Dockerfile de produção.
13
+ O tell de um Dockerfile amador é reconhecível:
14
+
15
+ - **Roda como `root`** porque nunca declarou `USER` — qualquer RCE no app vira root no host.
16
+ - **Uma imagem de 1.2 GB** porque empacotou compilador, headers e cache do gerenciador de
17
+ pacotes junto com o binário de runtime.
18
+ - **`COPY . .` no topo** — toda mudança em qualquer arquivo (até um `README`) invalida o cache
19
+ de `npm install`/`pip install` e o build inteiro roda de novo.
20
+ - **`FROM node:latest`** — build não-reproduzível; a `latest` de amanhã não é a de hoje.
21
+ - **`ARG NPM_TOKEN` / `ENV AWS_SECRET=...`** — o segredo fica gravado para sempre numa layer e
22
+ aparece em `docker history`.
23
+ - **`RUN apt-get install ...`** em uma layer e `rm -rf /var/lib/apt/lists/*` em outra — a layer
24
+ intermediária ainda carrega o cache; limpar depois não remove nada do tamanho final.
25
+
26
+ Tudo isso passa no `docker build`. Nenhum disso passa numa revisão séria. O conhecimento abaixo
27
+ é a régua.
28
+
29
+ ## O conhecimento
30
+
31
+ ### 1. `USER` não-root antes do `CMD` — sempre
32
+
33
+ Container que sobe como `root` é o default do Docker, e é o default errado. Crie um usuário
34
+ dedicado e troque para ele **antes** do `CMD`/`ENTRYPOINT`.
35
+
36
+ **Ruim:**
37
+ ```dockerfile
38
+ FROM node:22-slim
39
+ WORKDIR /app
40
+ COPY . .
41
+ RUN npm ci --omit=dev
42
+ CMD ["node", "server.js"] # roda como root (UID 0)
43
+ ```
44
+
45
+ **Bom:**
46
+ ```dockerfile
47
+ FROM node:22-slim
48
+ WORKDIR /app
49
+
50
+ # cria grupo/usuário com UID/GID explícito (determinístico entre rebuilds)
51
+ RUN groupadd --system --gid 10001 app \
52
+ && useradd --system --uid 10001 --gid app --no-create-home app
53
+
54
+ COPY --chown=app:app . .
55
+ RUN npm ci --omit=dev
56
+
57
+ USER 10001:10001 # numérico: o Kubernetes valida runAsNonRoot
58
+ CMD ["node", "server.js"]
59
+ ```
60
+
61
+ Por que UID/GID **numérico e explícito**: a doc oficial avisa que usuários criados sem UID
62
+ recebem um valor não-determinístico ("o próximo livre"), que pode mudar entre rebuilds. E com
63
+ `USER nonroot:nonroot` (nome), o `runAsNonRoot: true` do Kubernetes não consegue provar que o
64
+ UID != 0 — ele só confia em UID numérico. Use `USER 10001:10001`, não `USER app`.
65
+
66
+ ### 2. Multi-stage build — deixe o compilador para trás
67
+
68
+ Ferramentas de build (compiladores, devDependencies, headers) não têm o que fazer na imagem de
69
+ runtime. Separe em estágios e copie só o artefato.
70
+
71
+ **Ruim** (toolchain inteiro no final, imagem gigante):
72
+ ```dockerfile
73
+ FROM golang:1.25
74
+ WORKDIR /src
75
+ COPY . .
76
+ RUN go build -o /bin/app ./cmd/app
77
+ CMD ["/bin/app"] # carrega o Go SDK inteiro em produção
78
+ ```
79
+
80
+ **Bom** (Go compila em `build`, runtime recebe só o binário):
81
+ ```dockerfile
82
+ # --- estágio de build ---
83
+ FROM golang:1.25 AS build
84
+ WORKDIR /src
85
+ COPY go.mod go.sum ./
86
+ RUN go mod download
87
+ COPY . .
88
+ RUN CGO_ENABLED=0 go build -o /bin/app ./cmd/app
89
+
90
+ # --- estágio de runtime ---
91
+ FROM gcr.io/distroless/static-debian12:nonroot
92
+ COPY --from=build /bin/app /app
93
+ USER 65532:65532
94
+ ENTRYPOINT ["/app"]
95
+ ```
96
+
97
+ `COPY --from=build` traz só `/bin/app`. O Go SDK e os intermediários ficam no estágio `build` e
98
+ nunca entram na imagem final.
99
+
100
+ ### 3. Base slim/distroless + pin por digest (sha256)
101
+
102
+ | Base | Tamanho aprox. | Tem shell/gerenciador de pacotes? | Quando usar |
103
+ |---|---|---|---|
104
+ | `node:22` / `python:3.13` | 1+ GB | sim, toolchain completa | só no estágio de build |
105
+ | `*-slim` (ex.: `node:22-slim`) | ~200 MB | sim, mínima | runtime de apps interpretados |
106
+ | `alpine:3.21` | < 6 MB + libs | sim (busybox, apk) | quando precisa de shell pequeno |
107
+ | `gcr.io/distroless/*:nonroot` | ~2–20 MB | **não** (sem shell, sem apt) | runtime de produção endurecido |
108
+
109
+ Distroless não tem shell — reduz superfície de ataque (sem `sh` para um atacante explorar) e já
110
+ vem com a variante `:nonroot` (UID 65532).
111
+
112
+ **Ruim** (tag móvel, build não-reproduzível):
113
+ ```dockerfile
114
+ FROM node:latest
115
+ ```
116
+
117
+ **Bom** (tag + digest — imutável mesmo que o publisher reescreva a tag):
118
+ ```dockerfile
119
+ FROM node:22.11-slim@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
120
+ ```
121
+
122
+ O digest `@sha256:...` garante o mesmo bit-a-bit em qualquer máquina e em qualquer momento. Tag
123
+ sozinha (`node:22-slim`) ainda é alvo móvel: o registry pode reescrever para onde ela aponta.
124
+
125
+ ### 4. Ordem das camadas para cache — deps antes do código
126
+
127
+ O Docker cacheia cada layer; quando uma instrução muda, **todas as seguintes** são reconstruídas.
128
+ Coloque o que muda raramente em cima (manifesto de dependências) e o que muda a cada commit
129
+ embaixo (código-fonte).
130
+
131
+ **Ruim** (qualquer mudança no código reinstala todas as deps):
132
+ ```dockerfile
133
+ COPY . .
134
+ RUN npm ci --omit=dev
135
+ ```
136
+
137
+ **Bom** (só `package*.json` invalida o `npm ci`; editar `src/` reaproveita o cache de install):
138
+ ```dockerfile
139
+ COPY package.json package-lock.json ./
140
+ RUN npm ci --omit=dev
141
+ COPY . .
142
+ ```
143
+
144
+ Mesmo padrão em outras stacks:
145
+ ```dockerfile
146
+ # Python
147
+ COPY requirements.txt .
148
+ RUN pip install --no-cache-dir -r requirements.txt
149
+ COPY . .
150
+ ```
151
+
152
+ ### 5. `.dockerignore` — não mande lixo (nem segredo) pro contexto
153
+
154
+ Sem `.dockerignore`, `COPY . .` empacota `.git/`, `node_modules/`, `.env` — infla o contexto e
155
+ pode vazar segredo para dentro da imagem.
156
+
157
+ **`.dockerignore` mínimo:**
158
+ ```dockerignore
159
+ .git
160
+ node_modules
161
+ .env
162
+ .env.*
163
+ *.log
164
+ Dockerfile
165
+ .dockerignore
166
+ **/__pycache__
167
+ dist
168
+ coverage
169
+ ```
170
+
171
+ ### 6. Limpeza na MESMA camada — sem cache vazando em layer
172
+
173
+ Layers são imutáveis e empilhadas: o que entra numa layer permanece no tamanho final mesmo que
174
+ uma layer **posterior** apague. Instale e limpe no **mesmo** `RUN`.
175
+
176
+ **Ruim** (o cache do apt já foi gravado na layer anterior; o `rm` posterior não reduz nada):
177
+ ```dockerfile
178
+ RUN apt-get update && apt-get install -y curl ca-certificates
179
+ RUN rm -rf /var/lib/apt/lists/*
180
+ ```
181
+
182
+ **Bom** (instala e limpa num único `RUN` → a layer já nasce enxuta):
183
+ ```dockerfile
184
+ RUN apt-get update \
185
+ && apt-get install -y --no-install-recommends curl ca-certificates \
186
+ && rm -rf /var/lib/apt/lists/*
187
+ ```
188
+
189
+ `--no-install-recommends` evita arrastar pacotes sugeridos que você não pediu. Em Alpine, o
190
+ equivalente é `apk add --no-cache`.
191
+
192
+ ### 7. Nunca segredos em `ENV`/`ARG` — use `RUN --mount=type=secret`
193
+
194
+ `ENV` persiste na imagem e é lido por qualquer `docker inspect`. `ARG` fica visível em
195
+ `docker history`. Nenhum dos dois serve para token/credencial — a própria doc avisa: *"não é
196
+ recomendado usar build arguments para passar segredos; eles são visíveis em `docker history`"*.
197
+
198
+ **Ruim** (token gravado para sempre, vaza em `docker history`):
199
+ ```dockerfile
200
+ ARG NPM_TOKEN
201
+ RUN npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN} && npm ci
202
+ ```
203
+
204
+ **Bom** (BuildKit monta o segredo só durante o `RUN`; nunca entra em layer nem no cache):
205
+ ```dockerfile
206
+ # syntax=docker/dockerfile:1
207
+ RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
208
+ npm ci --omit=dev
209
+ ```
210
+ Build:
211
+ ```bash
212
+ DOCKER_BUILDKIT=1 docker build \
213
+ --secret id=npmrc,src=$HOME/.npmrc -t app .
214
+ ```
215
+
216
+ O arquivo montado existe só no sistema de arquivos daquele `RUN` e desaparece depois — não fica
217
+ na imagem nem no build cache.
218
+
219
+ ### 8. `HEALTHCHECK` — o orquestrador precisa saber se o app está vivo
220
+
221
+ Sem `HEALTHCHECK`, o Docker só sabe se o **processo** existe, não se o app **responde**. Um app
222
+ travado mas com PID vivo é reportado como saudável.
223
+
224
+ **Bom:**
225
+ ```dockerfile
226
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
227
+ CMD curl -fsS http://localhost:3000/health || exit 1
228
+ ```
229
+
230
+ Defaults do Docker quando você só passa `CMD`: `--interval=30s`, `--timeout=30s`, `--retries=3`,
231
+ `--start-period=0s`. Ajuste `--start-period` para a janela de boot do app (evita marcar
232
+ `unhealthy` durante a partida). Se a base for distroless (sem `curl`), use o healthcheck do
233
+ orquestrador ou compile um probe binário no estágio de build.
234
+
235
+ ### 9. `COPY` específico em vez de `COPY . .`
236
+
237
+ `COPY . .` arrasta tudo e invalida cache à toa. Copie só o que cada estágio precisa.
238
+
239
+ **Ruim:**
240
+ ```dockerfile
241
+ COPY . .
242
+ ```
243
+
244
+ **Bom:**
245
+ ```dockerfile
246
+ COPY package.json package-lock.json ./
247
+ COPY src ./src
248
+ COPY public ./public
249
+ ```
250
+
251
+ Bônus: combine com `--chown` para já entregar a posse ao usuário não-root sem um `RUN chown`
252
+ extra (`COPY --chown=10001:10001 src ./src`).
253
+
254
+ ## Checklist
255
+
256
+ Antes de marcar um Dockerfile como pronto para produção — qualquer "não" é bloqueio:
257
+
258
+ - [ ] Existe `USER <uid numérico>` (≠ 0) **antes** do `CMD`/`ENTRYPOINT`?
259
+ - [ ] O build é multi-stage e o estágio final **não** contém compilador/devDependencies?
260
+ - [ ] A base de runtime é `slim`/`distroless`/`alpine` (não a tag "gorda")?
261
+ - [ ] Todo `FROM` está pinado por **tag + `@sha256:` digest**?
262
+ - [ ] O manifesto de deps é copiado e instalado **antes** de copiar o código-fonte?
263
+ - [ ] Existe `.dockerignore` cobrindo `.git`, `node_modules`, `.env*`, build artifacts?
264
+ - [ ] Instalação de pacotes e limpeza (`rm -rf /var/lib/apt/lists/*`) estão no **mesmo** `RUN`?
265
+ - [ ] `--no-install-recommends` (apt) ou `--no-cache` (apk) presente?
266
+ - [ ] Zero segredos em `ENV`/`ARG`; tokens entram via `RUN --mount=type=secret`?
267
+ - [ ] Existe `HEALTHCHECK` apontando para um endpoint real de saúde?
268
+ - [ ] Os `COPY` são específicos (sem `COPY . .` no topo cacheável)?
269
+ - [ ] `COPY --chown` entrega a posse ao usuário não-root (sem `RUN chown` extra)?
270
+
271
+ ## Tabela de decisão
272
+
273
+ | Situação | Faça |
274
+ |---|---|
275
+ | App compilado (Go, Rust, binário estático) | Multi-stage → final em `gcr.io/distroless/static-debian12:nonroot` |
276
+ | App interpretado (Node, Python) em produção | Multi-stage → final em `*-slim` ou `distroless/nodejs`/`distroless/python3`, `USER` numérico |
277
+ | Precisa de shell para debug em runtime | `*-slim` ou `alpine`; nunca a base "gorda" |
278
+ | Precisa instalar pacotes do sistema | Um único `RUN apt-get update && install --no-install-recommends ... && rm -rf /var/lib/apt/lists/*` |
279
+ | Precisa de token/credencial no build (npm, pip privado, git) | `RUN --mount=type=secret` + `docker build --secret` — nunca `ARG`/`ENV` |
280
+ | Build lento, reconstrói deps a cada commit | Copie manifesto (`package.json`/`requirements.txt`) e instale **antes** de `COPY . .` |
281
+ | Quer reprodutibilidade total entre máquinas/CI | Pin de `FROM` por `@sha256:` digest |
282
+ | Distroless sem `curl` mas precisa de HEALTHCHECK | Probe via orquestrador, ou compile um healthcheck binário no estágio de build |
283
+ | Config dinâmica em runtime (não-secreta) | `ENV` é ok; para secreta, injete em runtime (orquestrador/secrets manager), não no Dockerfile |
@@ -0,0 +1,398 @@
1
+ ---
2
+ id: twelve-factor-app
3
+ domain: devops
4
+ agents: [devops]
5
+ when: "ao avaliar se uma aplicação está pronta para cloud / deploy contínuo"
6
+ ---
7
+
8
+ # Twelve-Factor App — o checklist enforçável de cloud-readiness
9
+
10
+ ## O problema
11
+
12
+ "Pronto pra produção" costuma ser uma opinião. O dono do serviço jura que está, o deploy quebra
13
+ às 3h da manhã, e ninguém consegue dizer *qual* propriedade faltou. O Twelve-Factor (Adam Wiggins,
14
+ Heroku) existe para transformar essa opinião em **12 checagens binárias** — cada fator é uma
15
+ pergunta com resposta sim/não, e cada "não" é uma classe específica de incidente futuro.
16
+
17
+ Os tells de uma app que **não** está pronta:
18
+
19
+ - Credencial de banco hardcoded num `config/database.yml` commitado → vaza no primeiro repo público.
20
+ - "Funciona na minha máquina" porque dev usa SQLite e prod usa Postgres.
21
+ - App precisa de Apache/Tomcat instalado e configurado *fora* dela pra subir.
22
+ - `kill -9` na app perde jobs porque ninguém trata SIGTERM.
23
+ - Escalar = comprar máquina maior, porque o processo guarda sessão em memória local.
24
+ - Migração de banco roda por SSH manual com um script que vive fora do repo e desincroniza.
25
+
26
+ Este pack é a régua. Para cada fator: o **anti-padrão concreto** + a **correção**, com snippet real —
27
+ não prosa. A regra-mãe do Twelve-Factor sobre config (Fator III) serve de litmus test pro espírito
28
+ todo: *"whether the codebase could be made open source at any moment, without compromising any
29
+ credentials."* Se você não consegue abrir o repo agora, falhou.
30
+
31
+ ## O conhecimento
32
+
33
+ ### I — Codebase: um codebase, muitos deploys
34
+
35
+ *"If there are multiple codebases, it's not an app – it's a distributed system."*
36
+ *"Multiple apps sharing the same code is a violation of twelve-factor."*
37
+
38
+ Correlação **1:1** entre codebase e app. Um codebase (em git), N deploys (prod, staging, local). Código
39
+ compartilhado entre dois apps vira **biblioteca** puxada via dependency manager — não um diretório
40
+ copiado nem um submódulo que dois serviços editam.
41
+
42
+ | Ruim | Bom |
43
+ |---|---|
44
+ | `api/` e `worker/` no mesmo repo, mas são dois apps com lifecycles diferentes → ainda 1 repo, OK; o problema é o inverso | 1 codebase = 1 app; dois apps que precisam do mesmo helper → extrai `lib-shared` versionada |
45
+ | `cp -r ../common ./vendor/common` em dois serviços | `common` publicado num registry privado; cada app declara `common@1.4.2` no manifest |
46
+ | Branch `production` com hotfixes que nunca voltam pra `main` | mesma codebase, deploys diferem só por *qual commit* + config |
47
+
48
+ > Deploy = instância rodando da app. Prod, staging e o laptop de cada dev são deploys do **mesmo**
49
+ > codebase, possivelmente em commits diferentes.
50
+
51
+ ### II — Dependencies: declare e isole, explicitamente
52
+
53
+ *"A twelve-factor app never relies on implicit existence of system-wide packages. It declares all
54
+ dependencies, completely and exactly, via a dependency declaration manifest."*
55
+ *"Dependency declaration and isolation must always be used together – only one or the other is not
56
+ sufficient."*
57
+
58
+ E o item esquecido: *"Twelve-factor apps also do not rely on the implicit existence of any system
59
+ tools. Examples include shelling out to ImageMagick or curl."*
60
+
61
+ ```dockerfile
62
+ # RUIM — depende de pacote do sistema e de uma tool implícita
63
+ FROM ubuntu:latest
64
+ RUN apt-get update && apt-get install -y python3 # versão imprevisível
65
+ COPY . /app
66
+ # em algum lugar do código: subprocess.run(["convert", "in.png", "out.jpg"]) # ImageMagick implícito
67
+ CMD ["python3", "/app/main.py"]
68
+ ```
69
+
70
+ ```dockerfile
71
+ # BOM — runtime pinado, deps declaradas e isoladas, tool de sistema vendorizada
72
+ FROM python:3.12-slim
73
+ # ImageMagick declarado, não assumido
74
+ RUN apt-get update && apt-get install -y --no-install-recommends imagemagick=8:6.* \
75
+ && rm -rf /var/lib/apt/lists/*
76
+ WORKDIR /app
77
+ COPY requirements.txt .
78
+ RUN pip install --no-cache-dir -r requirements.txt # isolamento via imagem + venv implícito
79
+ COPY . .
80
+ CMD ["python", "main.py"]
81
+ ```
82
+
83
+ Manifesto + isolamento juntos: `requirements.txt` + venv/container, `package.json` + `node_modules`
84
+ (com lockfile), `Gemfile` + Bundler, `go.mod`. Lockfile commitado é obrigatório — `^1.2.0` sem lock
85
+ não é declaração exata. Litmus: *um dev novo sobe o ambiente com só o runtime da linguagem + o
86
+ dependency manager*, nada de "ah, instala o ImageMagick na mão primeiro".
87
+
88
+ ### III — Config: no ambiente, nunca no código
89
+
90
+ *"An app's config is everything that is likely to vary between deploys."*
91
+ *"The twelve-factor app stores config in environment variables."*
92
+ *"A litmus test... is whether the codebase could be made open source at any moment, without
93
+ compromising any credentials."*
94
+
95
+ Config = o que varia entre deploys (credenciais, handles de backing service, hostnames). **Não** é
96
+ config: roteamento interno, wiring de módulos — isso é código e fica no código.
97
+
98
+ ```python
99
+ # RUIM — segredo no código, "ambientes nomeados" (combinatorial explosion)
100
+ DATABASE = {
101
+ "development": "postgres://localhost/myapp_dev",
102
+ "production": "postgres://admin:S3cr3t@prod-db.internal/myapp", # vaza no git
103
+ }
104
+ db_url = DATABASE[os.environ.get("RAILS_ENV", "development")]
105
+ ```
106
+
107
+ ```python
108
+ # BOM — env var granular, ortogonal, fora do código
109
+ db_url = os.environ["DATABASE_URL"] # falha alto se ausente; nunca tem default de prod
110
+ ```
111
+
112
+ Anti-padrão central: **agrupar config em ambientes nomeados** (`development`/`staging`/`production`).
113
+ *"This method does not scale cleanly... a combinatorial explosion of config which makes managing
114
+ deploys of the app very brittle."* Cada var é um controle **granular e ortogonal**: `DATABASE_URL`,
115
+ `REDIS_URL`, `STRIPE_KEY` — não um bloco `production`.
116
+
117
+ | Sinal | Veredito |
118
+ |---|---|
119
+ | `grep -rE 'password\|secret\|api_key\|://.*:.*@' src/` retorna algo | FALHA — segredo no código |
120
+ | `.env` no `.gitignore`; `.env.example` commitado só com chaves vazias | OK |
121
+ | Código tem `if env == "production"` ramificando comportamento | Cheiro de ambiente nomeado |
122
+
123
+ ### IV — Backing services: recursos anexáveis
124
+
125
+ *"The code for a twelve-factor app makes no distinction between local and third party services."*
126
+ *"...swap out a local MySQL database with one managed by a third party (such as Amazon RDS) without
127
+ any changes to the app's code."* — *"resources can be attached to and detached from deploys at will."*
128
+
129
+ Todo serviço consumido pela rede (Postgres, MySQL, RabbitMQ/Beanstalkd, SMTP/Postmark, Memcached/
130
+ Redis, S3) é um **recurso anexável**, acessado por um *handle* (URL) que vem da config (Fator III).
131
+ Trocar o Postgres local pelo RDS gerenciado = mudar uma env var, **zero deploy de código**.
132
+
133
+ ```yaml
134
+ # RUIM — host do broker hardcoded; trocar de provedor exige rebuild
135
+ services:
136
+ worker:
137
+ environment:
138
+ AMQP: "amqp://guest:guest@localhost:5672" # colado, e ainda com credencial
139
+ ```
140
+
141
+ ```yaml
142
+ # BOM — handle injetado; local e gerenciado são indistinguíveis pro código
143
+ services:
144
+ worker:
145
+ environment:
146
+ AMQP_URL: ${AMQP_URL} # amqp://localhost em dev, CloudAMQP em prod — mesma app
147
+ ```
148
+
149
+ Litmus: se o banco corromper, o ops troca o `DATABASE_URL` pra um réplica/backup e sobe — **sem
150
+ tocar no código**. Se precisa editar e rebuildar, o serviço não está "anexado", está soldado.
151
+
152
+ ### V — Build, release, run: três estágios estritamente separados
153
+
154
+ *"The twelve-factor app uses strict separation between the build, release, and run stages."*
155
+ *"Releases are an append-only ledger and a release cannot be mutated once it is created. Any change
156
+ must create a new release."*
157
+
158
+ - **Build**: código → bundle executável (compila, instala deps, gera assets). Tolera complexidade.
159
+ - **Release**: build + config daquele deploy → release com **ID único** (`v100`, `2011-04-06-20:32:17`),
160
+ **imutável**.
161
+ - **Run**: executa a release no ambiente. Deve ser mínimo (quebra de madrugada, sem dev por perto).
162
+
163
+ ```bash
164
+ # RUIM — mutar código no servidor de run (sem release, sem rollback)
165
+ ssh prod 'cd /app && git pull && npm install && pm2 restart all'
166
+ ```
167
+
168
+ ```bash
169
+ # BOM — build artefato imutável, release com ID, run só seleciona a release
170
+ docker build -t myapp:$GIT_SHA . # BUILD
171
+ docker push registry/myapp:$GIT_SHA # release = imagem + config no orquestrador
172
+ kubectl set image deploy/myapp app=registry/myapp:$GIT_SHA # RUN seleciona a release
173
+ # rollback = reapontar pra release anterior; nunca "editar em produção"
174
+ ```
175
+
176
+ Teste: dá pra fazer **rollback** pra release anterior por ID, instantâneo, sem rebuildar? Se não, os
177
+ estágios estão misturados. Não se altera código em runtime — *"changes cannot propagate back to the
178
+ build stage."*
179
+
180
+ ### VI — Processes: stateless, share-nothing
181
+
182
+ *"Twelve-factor processes are stateless and share-nothing."*
183
+ *"Sticky sessions are a violation of twelve-factor and should never be used or relied upon."*
184
+
185
+ Memória/filesystem do processo = *"a brief, single-transaction cache"* — nunca assuma que algo
186
+ cacheado estará lá num request futuro (*"a future request will be served by a different process"*).
187
+ Qualquer dado que precisa persistir vai pra **backing service** (Fator IV).
188
+
189
+ ```js
190
+ // RUIM — estado na memória do processo + sticky session
191
+ const sessions = {}; // some no restart/scale
192
+ app.post("/login", (req, res) => { sessions[req.ip] = user; });
193
+ // e no LB: ip_hash; → gruda usuário no mesmo processo
194
+ ```
195
+
196
+ ```js
197
+ // BOM — estado em datastore com expiração; processo é descartável
198
+ app.use(session({
199
+ store: new RedisStore({ client: redis }), // Redis/Memcached, com TTL
200
+ // qualquer instância serve qualquer request
201
+ }));
202
+ ```
203
+
204
+ Upload chega num processo, processamento num outro? Não pode depender do arquivo estar no disco local
205
+ — manda pro S3 (handle na config). Sticky session é o tell #1 de violação aqui.
206
+
207
+ ### VII — Port binding: self-contained, exporta via porta
208
+
209
+ *"The web app exports HTTP as a service by binding to a port, and listening to requests coming in on
210
+ that port."* A app inclui o **webserver como biblioteca** (Tornado, Jetty, o `http` embutido) —
211
+ não roda *dentro* de um container Apache/Tomcat injetado em runtime.
212
+
213
+ ```xml
214
+ <!-- RUIM — app é um .war que depende de um Tomcat externo instalado e configurado -->
215
+ <!-- deploy = copiar para /opt/tomcat/webapps/ e rezar pra versão bater -->
216
+ ```
217
+
218
+ ```js
219
+ // BOM — a própria app faz bind na porta vinda da config
220
+ const port = process.env.PORT || 3000;
221
+ app.listen(port, () => console.log(`bound on :${port}`));
222
+ // em prod, a routing layer mapeia o hostname público -> esta porta
223
+ ```
224
+
225
+ *"One app can become the backing service for another app, by providing the URL... as a resource
226
+ handle in the config for the consuming app."* — é o Fator IV fechando o círculo. Estende além de
227
+ HTTP (Redis protocol, XMPP). Teste: `docker run -p 8080:PORT myapp` responde sem nenhum webserver
228
+ externo? Então passa.
229
+
230
+ ### VIII — Concurrency: escale horizontal via process model
231
+
232
+ *"In the twelve-factor app, processes are a first class citizen."*
233
+ *"Twelve-factor app processes should never daemonize or write PID files."*
234
+
235
+ Tipos de processo (web, worker) com workloads distintos; escala = **adicionar processos** (a *process
236
+ formation*), não engordar um. Lifecycle (output, crash recovery, shutdown) é do **process manager**
237
+ externo (systemd, Kubernetes, Foreman) — a app não se daemoniza.
238
+
239
+ ```ini
240
+ # RUIM — um processo monolítico que se daemoniza e escreve PID
241
+ [program]
242
+ command=/app/server --daemon --pidfile=/var/run/app.pid # escala só vertical
243
+ ```
244
+
245
+ ```procfile
246
+ # BOM — Procfile: tipos de processo, foreground, escala horizontal
247
+ web: gunicorn app:server --bind 0.0.0.0:$PORT
248
+ worker: celery -A app worker --loglevel=info
249
+ # escala: kubectl scale deploy/web --replicas=10 (mais processos, não maior)
250
+ ```
251
+
252
+ Tells de violação: `--daemon`, `nohup ... &`, escrever `.pid`, ou "vamos aumentar a RAM da VM" como
253
+ única estratégia de escala. O processo roda em foreground; o orquestrador cuida do resto.
254
+
255
+ ### IX — Disposability: startup rápido, shutdown gracioso, robusto à morte súbita
256
+
257
+ *"Processes shut down gracefully when they receive a SIGTERM signal from the process manager."*
258
+ *"Ideally, a process takes a few seconds from the time the launch command is executed until the
259
+ process is up and ready to receive requests or jobs."*
260
+
261
+ - **Startup rápido** (segundos) → scale e deploy ágeis.
262
+ - **SIGTERM gracioso**: web para de aceitar novas conexões e finaliza as em curso; worker *"returning
263
+ the current job to the work queue"*.
264
+ - **Robusto à morte súbita** (`kill -9`, falha de hardware): jobs **idempotentes/reentrantes** e
265
+ backend de fila que devolve o job no disconnect (RabbitMQ NACK, Beanstalkd auto-return).
266
+
267
+ ```js
268
+ // RUIM — ignora SIGTERM; orquestrador manda SIGKILL após o grace period e perde requests/jobs
269
+ // (nenhum handler) → conexões em curso morrem no meio
270
+ ```
271
+
272
+ ```js
273
+ // BOM — drena conexões no SIGTERM
274
+ process.on("SIGTERM", async () => {
275
+ server.close(() => console.log("http drained")); // para de aceitar, termina em curso
276
+ await worker.close(); // devolve job atual à fila
277
+ process.exit(0);
278
+ });
279
+ ```
280
+
281
+ Teste: `docker stop` (= SIGTERM + grace) não derruba request em andamento nem perde job? E um
282
+ `kill -9` no worker faz o job voltar pra fila e ser reprocessado sem efeito colateral? Então passa.
283
+
284
+ ### X — Dev/prod parity: mantenha dev, staging e prod o mais parecidos possível
285
+
286
+ *"The twelve-factor developer resists the urge to use different backing services between development
287
+ and production, even when adapters theoretically abstract away any differences."*
288
+
289
+ Três gaps a fechar:
290
+
291
+ | Métrica | App tradicional | Twelve-Factor App |
292
+ |---|---|---|
293
+ | Tempo entre deploys (time gap) | Semanas | Horas |
294
+ | Autores do código vs quem faz deploy (personnel gap) | Pessoas diferentes | Mesmas pessoas |
295
+ | Dev vs prod (tools gap) | Divergentes | O mais parecidos possível |
296
+
297
+ ```yaml
298
+ # RUIM — SQLite em dev, Postgres em prod (mesmo "com ORM, é igual")
299
+ # dev: DATABASE_URL=sqlite:///dev.db
300
+ # prod: DATABASE_URL=postgres://...
301
+ # resultado: bug de tipo/locking só aparece em produção, depois do merge
302
+ ```
303
+
304
+ ```yaml
305
+ # BOM — mesmo backing service em todo ambiente, via container
306
+ # docker-compose.yml (dev) sobe o MESMO Postgres da prod
307
+ services:
308
+ db:
309
+ image: postgres:16 # mesma major version que prod
310
+ ```
311
+
312
+ Docker/Compose tornaram "rodar o serviço de verdade localmente" barato — não há mais desculpa de
313
+ custo pra SQLite-em-dev. O risco de incompatibilidade *"undermines continuous deployment"*.
314
+
315
+ ### XI — Logs: event stream pra stdout, sem gerenciar arquivo
316
+
317
+ *"A twelve-factor app never concerns itself with routing or storage of its output stream."*
318
+ Cada processo escreve seu event stream **unbuffered pra stdout**. Em dev, o dev vê no terminal; em
319
+ prod, *"each process' stream will be captured by the execution environment... and routed to one or
320
+ more final destinations."*
321
+
322
+ ```python
323
+ # RUIM — app gerencia arquivo, rotação, destino (responsabilidade que não é dela)
324
+ logging.basicConfig(
325
+ filename="/var/log/myapp/app.log", # disco local some no scale (Fator VI)
326
+ handlers=[RotatingFileHandler(...)], # rotação é trabalho do ambiente
327
+ )
328
+ ```
329
+
330
+ ```python
331
+ # BOM — escreve pra stdout, sem buffer; o ambiente coleta e roteia
332
+ import sys, logging
333
+ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
334
+ # (em Python, garanta unbuffered: PYTHONUNBUFFERED=1 no ambiente)
335
+ ```
336
+
337
+ Tells: a app abre `FileHandler`, faz rotação, ou tenta mandar pro Datadog/ELK direto. Não — ela só
338
+ emite linhas em stdout (JSON estruturado é ótimo); coleta, agregação e roteamento são do execution
339
+ environment (fluentd, Loki, CloudWatch).
340
+
341
+ ### XII — Admin processes: tarefas one-off no ambiente idêntico
342
+
343
+ *"One-off admin processes should be run in an identical environment as the regular long-running
344
+ processes of the app."* — contra a mesma release, mesma codebase e config. E o cuidado:
345
+ *"Admin code must ship with application code to avoid synchronization issues."*
346
+
347
+ ```bash
348
+ # RUIM — migração roda por um script que vive FORA do repo, com conexão própria
349
+ ssh dba@prod 'psql $PROD -f /home/dba/manual_migrations/add_column.sql'
350
+ # desincroniza do código; usa env/credencial diferente do app
351
+ ```
352
+
353
+ ```bash
354
+ # BOM — migração versionada no repo, mesma release/config, mesmo isolamento de deps
355
+ kubectl run migrate --image=registry/myapp:$GIT_SHA --restart=Never \
356
+ --env-from=secretRef:myapp-config -- python manage.py migrate
357
+ # REPL idem: bundle exec rails console / python manage.py shell
358
+ ```
359
+
360
+ A migração e o console usam o **mesmo bundle de deps** dos processos longos (se web é
361
+ `bundle exec thin start`, migração é `bundle exec rake db:migrate`). Tell: scripts de manutenção que
362
+ não estão no repo, ou que conectam com credenciais/imagem diferentes da app.
363
+
364
+ ## Checklist
365
+
366
+ Marque cada item. Qualquer "não" é cloud-readiness incompleto.
367
+
368
+ - [ ] **I Codebase** — 1 repo = 1 app; código compartilhado é biblioteca versionada, não cópia/submódulo editado.
369
+ - [ ] **II Deps** — manifesto + lockfile commitados; runtime pinado; nenhuma tool de sistema (curl, ImageMagick) assumida.
370
+ - [ ] **III Config** — `grep` por segredo no código volta vazio; tudo em env var granular; repo poderia virar público agora.
371
+ - [ ] **IV Backing services** — trocar Postgres/SMTP/Redis = mudar uma env var, zero deploy de código.
372
+ - [ ] **V Build/release/run** — artefato imutável, release com ID, rollback por ID instantâneo; nada de `git pull` em prod.
373
+ - [ ] **VI Processes** — stateless; sessão/estado em datastore (Redis/Postgres); zero sticky session.
374
+ - [ ] **VII Port binding** — app faz bind na `$PORT` por conta própria; não precisa de Apache/Tomcat externo.
375
+ - [ ] **VIII Concurrency** — process types num Procfile; escala = mais réplicas; sem daemonizar nem PID file.
376
+ - [ ] **IX Disposability** — handler de SIGTERM drena conexões/jobs; startup em segundos; jobs idempotentes; sobrevive a `kill -9`.
377
+ - [ ] **X Dev/prod parity** — mesmo backing service (mesma major) em dev e prod; deploys em horas, não semanas.
378
+ - [ ] **XI Logs** — app escreve só pra stdout unbuffered; não abre arquivo, não rotaciona, não roteia.
379
+ - [ ] **XII Admin processes** — migração/console versionados no repo, contra a mesma release e config.
380
+
381
+ ## Tabela de decisão
382
+
383
+ | Sintoma observado | Fator violado | Correção imediata |
384
+ |---|---|---|
385
+ | Credencial encontrada com `grep` no código-fonte | III | Mover pra env var; rotacionar o segredo vazado; `.env` no gitignore |
386
+ | `if env == "production"` ramificando lógica | III | Substituir por env vars granulares e ortogonais |
387
+ | Trocar de banco/provedor exige editar e rebuildar | IV | Acessar via handle/URL injetado pela config |
388
+ | Deploy é `ssh + git pull + restart` | V | Build de imagem imutável com ID; run seleciona release; rollback por ID |
389
+ | `kill -9` perde sessões ou jobs em andamento | VI + IX | Estado em Redis/Postgres; handler de SIGTERM; jobs idempotentes |
390
+ | LB com `ip_hash` / sticky session | VI | Session store compartilhado com TTL (Redis/Memcached) |
391
+ | App precisa de Tomcat/Apache pré-instalado pra subir | VII | Bundlar webserver como lib; bind em `$PORT` |
392
+ | Única forma de escalar é VM maior | VIII | Process types + réplicas horizontais; remover daemonização/PID |
393
+ | `--daemon`, `nohup &`, `.pid` no comando de start | VIII | Rodar em foreground sob process manager (systemd/k8s) |
394
+ | Bug só aparece em prod (SQLite em dev, Postgres em prod) | X | Mesmo backing service em todos os ambientes via container |
395
+ | App grava em `/var/log/app.log` e rotaciona | XI | Escrever em stdout unbuffered; deixar coleta/roteamento pro ambiente |
396
+ | Migração roda por script fora do repo / credencial própria | XII | Versionar no repo; rodar contra a mesma release/config/imagem |
397
+ | Sem lockfile; deps com `^`/`~` sem trava | II | Commitar lockfile; pinar runtime |
398
+ | Dois serviços editam o mesmo diretório de código copiado | I | Extrair pra biblioteca versionada no registry |