# ADR-005 — Runtime em containers no layout padrão do WHM; Apache como proxy burro

| Campo | Valor |
|---|---|
| **Status** | Aceito (2026-10-16) |
| **Data** | 2026-10-14 |
| **Plano de origem** | `wiki/plan/01-analise-e-decisoes.md` |
| **Relacionadas** | ADR-006, ADR-007, ADR-008, ADR-020, ADR-021, ADR-022, ADR-024, ADR-030, ADR-031 |

## Contexto

O operacional do servidor é **padrão de mercado** e não deve mudar: as equipes fazem
`git clone --depth=1` **dentro de `/home/<tenant>/public_html`** e deixam o WHM/cPanel agir
sobre essa pasta. Isso evita atritos de onboarding e mantém a operação conhecida.

O efeito colateral é que o cPanel **escreve arquivos de configuração no docroot**: `.user.ini`,
`php.ini`, `.htaccess` e `error_log`. Como o repositório é **bind-montado**, esses arquivos
aparecem dentro do container — e `.user.ini` é lido por **qualquer PHP-FPM, inclusive o nosso**.

A tentativa anterior de "separação física" (app fora do docroot) foi **descartada** por atrito
operacional. A decisão é: **manter o layout padrão** e fazer o **container ignorar** os arquivos
do painel, por **configuração** (sem apagá-los — o cPanel os recria).

## Decisão

**Layout padrão preservado; o container é dono do runtime e ignora os artefatos do cPanel.**

1. **Estrutura de arquivos inalterada:** repo em `/home/<tenant>/public_html`
   (`git clone --depth=1`), bind-montado nos containers como **`/var/www/html:Z`**. O WHM/cPanel
   continua agindo sobre a pasta.
2. **O container ignora, por configuração, os arquivos do painel:**

   | Arquivo do cPanel | Como é ignorado |
   |---|---|
   | `.user.ini` | FPM com **`user_ini.filename=`** (vazio) + `php_admin_value[user_ini.filename]=` → PHP não lê nenhum `.user.ini`. |
   | `php.ini` (docroot) | `PHPRC=/usr/local/etc/php` e `PHP_INI_SCAN_DIR=/usr/local/etc/php/conf.d` fixos → o `php.ini` do docroot **não** entra no scan. |
   | `.htaccess` | O **Caddy nunca lê `.htaccess`**; o Apache **não mapeia** o app (`ProxyPass /`) e usa `AllowOverride None` no docroot. |
   | `error_log` | `php_admin_value[error_log]=/proc/self/fd/2`; o Caddy **nega** servir `error_log`, `*.ini` e dotfiles. |

3. **Conexão mínima com o WHM** (o único acoplamento): certificados/**AutoSSL**, **domínios** e
   **Apache 2.4**. O Apache é um **proxy burro**:
   ```apache
   ProxyPreserveHost On
   ProxyPass        /.well-known !        # AutoSSL
   ProxyPass        / http://127.0.0.1:${WEB_PORT}/
   ProxyPassReverse / http://127.0.0.1:${WEB_PORT}/
   ```
   Include em `/etc/apache2/conf.d/userdata/{std,ssl}/2_4/<acct>/<domain>/app.conf`
   (sobrevive a `rebuildhttpdconf`). Ver `wiki/specs/16-edge-apache-whm.md`.
4. **`web` (Caddy 2)** é o único servidor HTTP do app (dev e produção), **non-root** escutando em
   **`:8080`** (`auto_https off`, `admin off`), e dono de headers/CSP/compressão/ETag
   (ADR-020/ADR-022). O Apache só encaminha bytes (**HTTP/1.1** com o Caddy; HSTS/HTTP2/3 são do
   Apache, que termina TLS). Guia: skill `caddy`.
5. **Runtime PHP** = `php-fpm` sobre a imagem oficial
   `php:8.4.27-fpm-trixie@sha256:e2d8e787…` (non-root via Compose);
   Dockerfile **fino** instala só as extensões faltantes + a extensão nativa `blake3` —
   ADR-008/ADR-029/ADR-031. Um serviço **`queue`** reusa **a mesma imagem** (`php artisan queue:work`), sem
   Horizon/Supervisor — o Docker reinicia (ADR-001/ADR-002).
6. **Storage em duas camadas:** o `storage/` do Laravel (logs, cache, framework, tmp) é **local**,
   com caminho configurável (`STORAGE_PATH`, ADR-006); os **arquivos do domínio** (mídia, anexos,
   PDFs, exports) vão para **object storage S3** — container **`storage`** (**RustFS**, ADR-030),
   interno e trocável pelo **AWS S3 do cliente só mudando `.env`**.
7. **Um comando de produção:** **`scripts/update_prod.sh`** na raiz sobe/atualiza com segurança e aplica
   **todas** as otimizações (framework, OPcache/JIT, composer autoritativo, cache de rotas/views).
   Ver `wiki/specs/17-up-prod.md` (ou seção no plano 04).
8. **Profiles `debug`/`telemetry` são dev-only** (ADR-024). A imagem `web`+`php-fpm` é a mesma
   em dev e produção.

```
Produção (host WHM/cPanel — layout padrão)
[Internet] → Apache 2.4 (TLS/AutoSSL/domínios) ──ProxyPass──▶ web (Caddy) ──fastcgi──▶ php-fpm
                 │ (só encaminha)                                  ├─ postgres:18    (rede interna)
                 └── public_html/ (repo clonado; .user.ini/         ├─ valkey:9       (rede interna)
                     php.ini/.htaccess/error_log = IGNORADOS)        ├─ storage:rustfs (S3 local)  ── ADR-030
                                                                     └─ (profiles) mailpit/otel
Storage: storage/ do Laravel local (STORAGE_PATH); mídia/anexos em S3 (RustFS → AWS S3 por .env)
```

## Justificativa (alternativas descartadas)

- **App fora do docroot** — descartada: atrito operacional (as equipes clonam em `public_html`);
  a decisão é manter o padrão e ignorar os arquivos do painel.
- **Apagar/neutralizar os arquivos do cPanel** (chmod/move) — descartada: o painel os recria e
  isso briga com o WHM; **ignorar por configuração** é estável e sem efeitos colaterais.
- **FastCGI direto (Apache serve estáticos)** — descartada: obriga o Apache a mapear o FS e
  reabre o jogo de `.htaccess`/`AllowOverride`.
- **Emular EA4/`ea-php84` + Apache num container** — descartada: acopla às limitações do WHM e
  duplica o edge.
- **Container com `:443`/TLS próprio (elimina o Apache)** — descartada por ora: perde o AutoSSL
  e a gestão de domínios do WHM. Registrada em `wiki/backlog/`.
- **Octane/FrankenPHP (app em memória)** — descartada: Caddy+FPM+OPcache+`optimize` já cobre o
  perfil I/O-bound; Octane exigiria bindings `scoped` e vigilância de memória. Reavaliar só com
  medição indicando CPU-bound.
- **Horizon/Pulse/Telescope** — descartadas por **mínimo de dependências**: fila nativa em Valkey
  sob Docker; observabilidade é JSON + `X-Request-ID` (ADR-024), com OTel **opcional**.

## Consequências

**Positivas**
- **Zero atrito operacional**: estrutura de arquivos e `git clone` permanecem os usuários.
- O cPanel continua cuidando de certificados/domínios/Apache; nós cuidamos do runtime.
- Arquivos do painel **ignorados por configuração** (sem apagar nada).
- Cache/ETag/compressão com **um só dono** (Caddy), favorecendo o ADR-022.
- Um único comando (`scripts/update_prod.sh`) para subir/atualizar com otimizações completas.

**Custos / negativas**
- Um **include no Apache do WHM** e `mod_proxy`/`mod_proxy_http` habilitados.
- Um container `web` a mais (Caddy) — barato diante da estabilidade ganha.
- É preciso **verificar** que o FPM realmente ignora `.user.ini` (teste no plano 04).
- Imagens **públicas pinadas por digest** (matriz completa no ADR-031); sem registro autenticado.

## Exemplo mínimo (comparativo)

```yaml
# ✅ Layout padrão do WHM: repo em public_html; container ignora os arquivos do painel
# Imagens pinadas por digest — matriz canônica no ADR-031.
services:
  web:     { image: "caddy:2.11.7@sha256:685ae3d7…", volumes: [ ".:/var/www/html:Z" ] }
  php-fpm: { build: { dockerfile: docker/php-fpm/Dockerfile } }   # FROM php:8.4-fpm-trixie + user_ini.filename=
  postgres:{ image: "postgres:18-trixie@sha256:74935e72…" }
  valkey:  { image: "valkey/valkey:9-alpine@sha256:48332870…" }
  storage: { image: "rustfs/rustfs:1.0.1-glibc@sha256:87763ed4…" }
```

```ini
; zz-custom.conf — o container ignora .user.ini
[www]
user_ini.filename =
php_admin_value[user_ini.filename] =
php_admin_value[error_log] = /proc/self/fd/2
```

## Verificação

- `docker compose exec php-fpm php -i | grep user_ini` → `.user.ini` **desligado**.
- Com um `public_html/.user.ini` contendo `display_errors=On`, a app **não** muda de
  comportamento (o arquivo é ignorado).
- `docker compose exec php-fpm php --ini` lista **apenas** o `conf.d` do container.
- `curl -I https://<domínio>/` passa pelo Caddy (headers do ADR-020); AutoSSL renova.
- `public_html/.htaccess` do cPanel **não** afeta a app (Caddy/ProxyPass).
- `ss -ltn`: `5432`/`6379` ausentes; `web` só em `127.0.0.1`.
- `./scripts/update_prod.sh` é idempotente e termina com `php artisan about` mostrando config/route/view/event
  cacheados.

## Referências

- `wiki/plan/04-runtime-containers.md`; `wiki/specs/16-edge-apache-whm.md`;
  `wiki/specs/17-up-prod.md`; `whm-simulator-docker/DECISOES_TECNICAS.md`;
  skill `caddy` (`.agents/skills/caddy/`).
