Repository navigation
Expand file tree
/
Copy pathDockerfile.dev
More file actions
157 lines (146 loc) · 9.71 KB
/
Copy pathDockerfile.dev
File metadata and controls
157 lines (146 loc) · 9.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
FROM hexpm/elixir:1.20.4-erlang-29.1.1-alpine-3.24.2@sha256:ad851f40ce103dcb4ad56f23877d99473ef5c013e09b9e57921ffe877ac6d6a9
# UID/GID do usuário que roda o container — ver docker/api/Dockerfile, mesmo
# mecanismo (docker-compose.yml `build.args` + .env.example). Fica no TOPO
# do arquivo por convenção, mas o grupo/usuário só é criado depois de TODAS
# as instalações que exigem root, no fim deste Dockerfile — trocar de
# usuário cedo demais quebraria todo `apk add`/`pip install` daqui pra baixo.
ARG DEV_UID=1000
ARG DEV_GID=1000
# git: Engine.Actions.Workspace precisa dele pra derivar o working tree
# do bare repo local provisionado (Fase 2) antes de rodar ações de terminal.
RUN apk add --no-cache build-base inotify-tools git
# gitleaks (SecOpsAgent, Fase 4a) — binário estático direto do release do
# GitHub (mais confiável em Alpine/musl do que empacotamento via gerenciador
# de pacote). BEST-EFFORT: se a versão/plataforma não bater ou a rede falhar
# no build, segue sem o binário — Engine.Actions.GitleaksDetector detecta a
# ausência (System.find_executable/1) e o SecOpsAgent pula o scanner (registra
# no resumo do parecer), nunca quebra o gate (ver docs/adr/0013).
#
# A versão é LOAD-BEARING: o detector usa o subcomando `gitleaks dir` (varre a
# árvore de trabalho), que só existe a partir do 8.19 — abaixo disso o scan
# falha e o gate roda sem verificação de segredo. Ver docs/adr/0020.
#
# hadolint (gate de QA de infra, Fase 4a) vem no MESMO bloco por ser o mesmo
# padrão de binário estático do GitHub, e pra aproveitar o `curl` do grupo
# virtual antes dele ser removido. SEM HADOLINT O GATE DE QA DE INFRA APROVA
# QUALQUER DOCKERFILE: `InfraGateRunner.lint_dockerfiles/1` trata a ausência
# como "pulado", não acha nada, e o veredito sai `approved` (ver ADR 0021).
# Mantido igual ao Dockerfile.prod: dev e produção rodando versões diferentes
# do scanner significa gate se comportando diferente nos dois lugares. Subiu de
# 8.21.2 para 8.30.1 na Fase 5 porque o binário do 8.21.2 carrega 15 CVEs de Go
# stdlib (ver .trivyignore.yaml). As regressões `--only gitleaks` passam nos dois.
#
# actionlint (subagente Workflows da área de Infra, Fase 8c) — mesmo padrão
# de binário estático do GitHub (tar.gz, igual gitleaks). SEM ACTIONLINT O
# WORKFLOWS GERA WORKFLOW SEM VALIDAÇÃO LOCAL: `Engine.Actions.
# ActionlintDetector` degrada graciosamente (`ValidateInfraFile` segue sem
# validar), nunca quebra o turno — ver ADR 0039. Só valida GitHub Actions;
# não há binário offline equivalente pra `.gitlab-ci.yml` (gap documentado).
# *_SHA256: mesmos valores do Dockerfile (mesmo binário,
# mesma versão) -- ver o comentário lá sobre como foram conferidos (download
# independente + checksums.txt publicado no release). Aqui o build continua
# BEST-EFFORT: um checksum que não bate entra no MESMO ramo `|| echo` de uma
# falha de rede ou binário ausente -- nunca quebra o build de dev --, mas o
# binário deixa de existir, e os detectores tratam a ausência como sempre
# trataram. Sem isto, um release comprometido ou MITM entregaria um binário
# diferente do esperado e o container de dev rodaria os gates contra ele, em
# silêncio.
ARG GITLEAKS_VERSION=8.30.1
ARG GITLEAKS_SHA256=551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
ARG HADOLINT_VERSION=2.12.0
ARG HADOLINT_SHA256=56de6d5e5ec427e17b74fa48d51271c7fc0d61244bf5c90e828aab8362d55010
ARG ACTIONLINT_VERSION=1.7.12
ARG ACTIONLINT_SHA256=8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
# `echo "$HASH arquivo" | sha256sum -c -` é um pipe, e o hadolint pede
# `-o pipefail` (DL4006). Não acrescenta nada aqui: `echo` de uma string fixa
# não falha, e se o `curl` anterior tivesse falhado o `&&` já teria pulado
# para o `|| echo "[build] ... nao instalado"` antes de chegar neste pipe.
# Suprimido com o motivo escrito, não por hábito (mesmo padrão do
# docker/web/Dockerfile.prod).
# hadolint ignore=DL4006
RUN apk add --no-cache --virtual .binaries-build curl tar \
&& (curl -fsSL -o /tmp/gitleaks.tar.gz \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
&& echo "${GITLEAKS_SHA256} /tmp/gitleaks.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/gitleaks.tar.gz -C /usr/local/bin gitleaks \
&& chmod +x /usr/local/bin/gitleaks \
|| echo "[build] gitleaks nao instalado -- SecOpsAgent detecta a ausencia em runtime") \
&& (curl -fsSL -o /usr/local/bin/hadolint \
"https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-Linux-x86_64" \
&& echo "${HADOLINT_SHA256} /usr/local/bin/hadolint" | sha256sum -c - \
&& chmod +x /usr/local/bin/hadolint \
|| echo "[build] hadolint nao instalado -- o gate de QA de infra pula o lint de Dockerfile") \
&& (curl -fsSL -o /tmp/actionlint.tar.gz \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
&& echo "${ACTIONLINT_SHA256} /tmp/actionlint.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/actionlint.tar.gz -C /usr/local/bin actionlint \
&& chmod +x /usr/local/bin/actionlint \
|| echo "[build] actionlint nao instalado -- o Workflows segue sem validar o pipeline gerado") \
&& rm -f /tmp/gitleaks.tar.gz /tmp/actionlint.tar.gz \
&& apk del .binaries-build
# semgrep (SecOpsAgent, Fase 4a) — via pip. python3/pip ficam instalados de
# verdade (não num grupo --virtual): o semgrep precisa deles em RUNTIME, não
# só pra instalar. Historicamente mais estável em glibc/Debian do que
# Alpine/musl — mesmo tratamento best-effort do gitleaks acima
# (Engine.Actions.SemgrepDetector degrada graciosamente se ausente).
#
# A versão é FIXA e é a MESMA do Dockerfile.prod (o spec
# scripts/ci/semgrep-do-engine.spec.ts reprova a divergência). Sem versão o pip
# pegou a 1.179.0, que não publica wheel `musllinux`: no Alpine ele monta o
# pacote do sdist, SEM o `semgrep-core`, e todo scan sai com exit 2 ("Failed to
# find semgrep-core") — o gate SecOps de dev ficava pendente para sempre.
# Subir a versão exige wheel musllinux publicada no PyPI.
ARG SEMGREP_VERSION=1.183.0
RUN apk add --no-cache python3 py3-pip \
&& (pip install --no-cache-dir --break-system-packages "semgrep==${SEMGREP_VERSION}" \
|| echo "[build] semgrep nao instalado -- SecOpsAgent detecta a ausencia em runtime")
# yamllint (gate de QA de infra, Fase 4a) — valida a sintaxe do compose e do
# pipeline de CI que o InfraAgent propõe. Aproveita o python3/pip do semgrep
# acima; nao ha parser de YAML nas deps do Elixir, e o padrao da casa pra
# validacao e ferramenta externa com deteccao opcional. Best-effort igual aos
# demais (Engine.Actions.YamlLintDetector degrada se ausente).
RUN pip install --no-cache-dir --break-system-packages yamllint \
|| echo "[build] yamllint nao instalado -- o gate de QA de infra pula a validacao de YAML"
# Node (dev): a suite de testes do PROJETO GERIDO roda aqui dentro — o
# `terminal` do DevAgent executa via Engine.Actions.TerminalExecutor, no
# worktree, dentro deste container. Sem o toolchain do projeto, `npm test`
# nunca dá exit 0 e o ReportDone jamais deixa abrir PR.
#
# LIMITAÇÃO CONHECIDA: isto não escala pra stacks arbitrárias — hoje o engine
# precisa ter instalado o toolchain de todo projeto que ele gerencia. A saída
# real é um sandbox por projeto (container/imagem própria por module_map),
# fora do escopo da Fase 4a.
RUN apk add --no-cache nodejs npm
# Só cria grupo/usuário quando o UID/GID pedido diverge do que a imagem já
# tem (mesma ressalva do docker/api/Dockerfile). USER abaixo referencia o
# par numérico, nunca o nome.
RUN (getent group "$DEV_GID" >/dev/null 2>&1 || addgroup -g "$DEV_GID" devuser) \
&& (getent passwd "$DEV_UID" >/dev/null 2>&1 || adduser -D -u "$DEV_UID" -G "$(getent group "$DEV_GID" | cut -d: -f1)" devuser)
# `mix local.hex`/`mix local.rebar` (no CMD abaixo) escrevem em $HOME/.hex e
# $HOME/.mix — caminhos que o docker-compose.yml monta como volumes NOMEADOS
# em /root/.hex e /root/.mix (RW, precisam sobreviver a rebuild). `_build` e
# `deps` são o mesmo caso dentro do WORKDIR. Um volume nomeado NOVO herda o
# dono do que já existe naquele caminho NA IMAGEM — por isso os diretórios
# nascem aqui, ANTES do USER, já com o dono certo: sem isso, o primeiro
# `mix deps.get` como devuser bateria em diretório ainda dono de root.
# ENV HOME=/root mantém os caminhos batendo com os volumes do compose em vez
# de exigir mexer neles — devuser não é root, mas é dono de /root aqui.
#
# `/data/git-repos` e `/data/project-workspaces` entram na MESMA linha e pelo
# MESMO motivo — são volumes nomeados do compose (`docker-compose.yml`, serviço
# `engine`), e sem existirem aqui nasciam dono de root. O engine não escreve em
# git-repos, mas ESCREVE em project-workspaces: é ali que ele materializa o
# working tree e os worktrees por dev agent (`Engine.Actions.Workspace.
# workspace_dir/2`). NÃO é o permissions.json — esse é lido e escrito só pela
# api (`decide.ts`); no engine, todas as menções a ele são comentário. Os dois
# caminhos ficam juntos porque são o mesmo par montado nos dois serviços, e
# separá-los faria a próxima pessoa consertar um e esquecer o outro. O
# Dockerfile.prod da api já fazia isto desde sempre — a lição existia e não
# tinha atravessado para o dev.
RUN mkdir -p /root/.mix /root/.hex /workspace/_build /workspace/deps \
/data/git-repos /data/project-workspaces \
&& chown -R "$DEV_UID:$DEV_GID" /root /workspace /data
ENV HOME=/root
WORKDIR /workspace
USER ${DEV_UID}:${DEV_GID}
CMD ["sh", "-c", "mix local.hex --force && mix local.rebar --force && mix deps.get && mix ecto.create && mix ecto.migrate && mix phx.server"]