Cross-arch : container-test accepte une platform cible (incident « exec format error »)

Le premier déploiement NAS a échoué avec « exec /docker-entrypoint.sh:
exec format error » : image arm64 (buildée sur Mac Apple Silicon) exécutée
sur un hôte amd64. Le test réel l'a épiné : container-test.sh accepte
désormais un paramètre PLATFORM (build cross-arch SANS émulation — le
Dockerfile n'a aucun RUN — puis run émulé + E2E complet) :
  bash scripts/container-test.sh <port> linux/amd64
→ validé vert sur linux/amd64 (healthcheck durci, E2E complet).

Docs §10 : leçon cross-arch (les 2 procédures de build — sur le serveur
cible ou multi-arch push via buildx), statut : les DEUX archs testées ;
note : l'« io_setup() failed » sous émulation Rosetta est inoffensif.
This commit is contained in:
Siphonight 2026-09-08 21:30:18 +02:00
parent 96e0627741
commit e078650045
3 changed files with 89 additions and 20 deletions

View File

@ -62,23 +62,32 @@ server {
### Avec Docker (self-host NAS/VPS)
L'image ne fait **que servir les fichiers** : aucune donnée dedans, la vie
privée est identique (les données vivent dans les navigateurs). Build et
exécution :
privée est identique (les données vivent dans les navigateurs). Deux
procédures équivalentes — **l'image doit être pour l'arch CPU de l'hôte**
(« exec format error » = arch incompatible, cf
[docs/DEVELOPPEMENT.md §10](docs/DEVELOPPEMENT.md)) :
```bash
docker compose up -d # build + run → http://<hôte>:8080/
docker compose up -d --build # rebuild après mise à jour du dépôt
# ── Option 1 (recommandée) : build SUR le serveur cible
git clone https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web && cd HormoneTrack-web
docker compose up -d --build # arch native, toujours correcte
# ── Option 2 : multi-arch depuis un Mac (push vers ton registry)
docker buildx build --platform linux/amd64,linux/arm64 \
-t tonuser/hormonetrack-web:latest --push .
```
- Image **non privilégiée** (`nginx-unprivileged`, utilisateur 101, port
8080), filesystem en **lecture seule** (composé prêt pour le NAS),
healthcheck intégré, gzip sur l'asset PK (550 Ko → ~150 Ko).
8080), filesystem en **lecture seule** + tmpfs (composé durci), vérif
healthcheck intégrée, gzip sur l'asset PK (550 Ko → ~150 Ko).
- Headers `no-cache` : une mise à jour d'image est prise en compte au
rechargement suivant, sans code périmé chez les clients.
- Validation en 3 commandes sur l'hôte qui a Docker (cf
[docs/DEVELOPPEMENT.md §10](docs/DEVELOPPEMENT.md)) : build, run, puis
`HRT_E2E_BASE=http://127.0.0.1:8080 node scripts/e2e.mjs` — le même test
E2E que le CI pilote le site SERVI PAR LE CONTENEUR.
E2E que le CI pilote le site SERVI PAR LE CONTENEUR. Pour tester l'arch
cible AVANT déploiement :
`bash scripts/container-test.sh 8979 linux/amd64`.
Aucun build, aucune variable d'environnement, aucun composant serveur : les
données de chaque personne vivent **dans SON navigateur**, jamais sur la

View File

@ -571,14 +571,54 @@ HRT_E2E_BASE=http://127.0.0.1:8080 node scripts/e2e.mjs
**Statut (8 sept. 2026)** : build et run RÉELS validés localement via
colima (daemon Docker léger, 2 CPU / 2 Go) — `scripts/container-test.sh`
vert en configuration DURCIE (healthcheck healthy, endpoints, headers,
gzip, MIME, E2E complet contre le conteneur). Deux problèmes réels trouvés
et corrigés au premier test : (a) tmpfs `/tmp` REQUIS en read-only (les
temporaires de nginx-unprivileged — « mkdir /tmp/proxy_temp failed »
sinon), (b) curl `%header_json` minuscule les clés (bug du TEST, pas du
conteneur). Si le durcissement pose problème sur ton NAS : retirer
`read_only`/`tmpfs`/`security_opt` du compose (aucune perte de sécurité
cruciale — le conteneur ne détient rien).
vert en configuration DURCIE pour **les DEUX architectures** (arm64 native
et linux/amd64 cross-compilée : healthcheck healthy, endpoints, headers,
gzip, MIME, E2E complet contre le conteneur). Problèmes réels trouvés aux
premiers tests, corrigés et documentés : (a) tmpfs `/tmp` REQUIS en
read-only (« mkdir /tmp/proxy_temp failed » sinon), (b) curl `%header_json`
minuscule les clés (bug du TEST), (c) **exec format error sur l'hôte
amd64** (cf §10 bis). Si le durcissement pose problème sur ton NAS :
retirer `read_only`/`tmpfs`/`security_opt` du compose (aucune perte de
sécurité cruciale — le conteneur ne détient rien).
### Cross-arch (ⓘ leçon du 8 sept. 2026 — « exec /docker-entrypoint.sh:
exec format error »)
`exec format error` au `docker run` = l'image est pour une AUTRE
architecture CPU que l'hôte (image arm64 buildée sur Mac → serveur amd64).
Le Dockerfile ne contient **AUCUN `RUN`** (labels + COPY + HEALTHCHECK) :
la cross-compilation est FAIBLE — buildx produit l'image pour n'importe
quelle platform SANS émulation de build (constaté : build amd64 instantané
sur l'arm64). Seul le RUN du conteneur émule (Rosetta via colima VZ / qemu
sur l'hôte de build — sous émulation nginx journalise un
`io_setup() failed (ENOSYS)` inoffensif : il retombe et répond 200 ; ce
syscall existe sur un vrai Linux amd64).
**Règle** : le conteneur de PROD doit être buildé pour L'ARCH de l'hôte —
deux procédures équivalentes :
1. **Sur le serveur cible** (recommandé — zéro registre, zéro émulation) :
```bash
git clone https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web && cd HormoneTrack-web
docker compose up -d --build # arch native du serveur, toujours bon
```
2. **Multi-arch depuis le Mac** (push vers un registry — Docker Hub, GHCR,
ou le registry interne du NAS) :
```bash
docker buildx build --platform linux/amd64,linux/arm64 -t <user>/hormonetrack-web:latest --push .
```
(build natif des deux archs — cf container-test.sh [platform] pour
tester CHAQUE arch localement avant push : l'E2E complet tourne contre
l'image cross-archémulée.)
**Test de l'arch cible AVANT push/déploiement** :
`bash scripts/container-test.sh <port> linux/amd64` — c'est le test qui
aurait attrapé l'incident « exec format error » du premier déploiement
NAS (image arm64 seule déployée sur hôte amd64).
Composition de l'utilisatrice (8 sept.) : service `image:` depuis un
registry, `env_file`/limites de ressources/réseaux externes — tout est
inoffensif pour ce conteneur statique (aucune variable lue, no volumes).
## 11. Bugs potentiels évités pendant le portage

View File

@ -12,7 +12,18 @@
# Prérequis : docker (daemon) ; node + playwright pour l'E2E final (skippé
# sinon — cf check.sh pour l'installation).
#
# Usage : bash scripts/container-test.sh [port] (défaut 8975)
# Usage : bash scripts/container-test.sh [port] [platform]
# [port] défaut 8975
# [platform] platform Docker optionnelle (ex. linux/amd64) — permet de
# tester l'image pour L'HÔTE CIBLE depuis un Mac arm64 : sans
# elle, on build+run l'arch native (arm64) ; avec platform,
# buildx produit l'image pour CETTE platform (build natif —
# le Dockerfile ne contient AUCUN RUN, cross-compile sans
# émulation de build) et le run passe par l'émulation de
# l'hôte (Rosetta/qemu). ⚠️ Le bug réel du 8 sept. —
# « exec /docker-entrypoint.sh: exec format error » sur le
# NAS amd64 — venait d'une image arm64 uniquement : C'EST CE
# TEST cross-arch qui l'eût attrapé.
#
# ⚠️ Un SEUL invocation fait tout (leçon Android #43 : les invocations
# rapprochées se remplacent mutuellement) et l'échec est BRUYANT (exit 1
@ -22,9 +33,17 @@ set -euo pipefail
cd "$(dirname "$0")/.." # racine du dépôt
PORT="${1:-8975}"
PLATFORM="${2:-}"
HOST_PORT="127.0.0.1:${PORT}"
IMAGE="hormonetrack-web:smoke"
CONTAINER="hrt-web-smoke"
# Suffixe de tag par platevorme pour ne jamais mélanger les images
TAG_SUFFIX="smoke"
PLATFORM_ARG=()
if [[ -n "${PLATFORM:-}" ]]; then
TAG_SUFFIX="smoke-${PLATFORM##*/}" # linux/amd64 → smoke-amd64
PLATFORM_ARG=(--platform "$PLATFORM")
fi
IMAGE="hormonetrack-web:${TAG_SUFFIX}"
CONTAINER="hrt-web-${TAG_SUFFIX}"
ROOM=0 # compteur d'échecs
fail() { echo " ✗ $1"; ROOM=$((ROOM + 1)); }
@ -35,8 +54,8 @@ cleanup() {
}
trap cleanup EXIT
echo "── 1. Build de l'image ──"
docker build -t "$IMAGE" . 2>&1 | tail -2
echo "── 1. Build de l'image${PLATFORM:+ [${PLATFORM}]} ──"
docker build "${PLATFORM_ARG[@]}" -t "$IMAGE" . 2>&1 | tail -2
echo "── 2. Run (durcissement compose : read_only + no-new-privileges) ──"
# On teste l'image dans la configuration DURCIE de docker-compose.yml —
@ -46,6 +65,7 @@ echo "── 2. Run (durcissement compose : read_only + no-new-privileges) ─
# « mkdir /tmp/proxy_temp failed » (constaté au premier container-test).
docker run -d --rm --name "$CONTAINER" \
-p "${HOST_PORT}:8080" \
"${PLATFORM_ARG[@]}" \
--read-only \
--tmpfs /var/cache/nginx --tmpfs /run --tmpfs /tmp \
--security-opt no-new-privileges \