# Écrire un Dockerfile pour Arkya

> Les quatre règles qui font qu'une image démarre chez nous, et les pièges qui la font tomber.

Ton image tourne dans un conteneur que le nœud fabrique. Il impose quelques
contraintes — peu nombreuses, mais fermes. Les respecter tient en quatre règles ; les
ignorer donne des symptômes qui ne ressemblent pas toujours à leur cause.

## Les quatre règles

<Steps>
  <Step title="Écouter sur $PORT">
    Arkya pose cette variable au démarrage, avec le port que le nœud publie réellement.
    Une application qui écoute en dur sur 3000 ne répondra jamais.

    ```js
    const port = Number(process.env.PORT ?? 3000);
    ```
  </Step>
  <Step title="Écouter sur 0.0.0.0">
    `127.0.0.1` ne sort pas du conteneur. C'est la deuxième cause de « ça tourne mais
    rien ne répond », juste après la précédente.

    ```js
    app.listen(port, "0.0.0.0");
    ```
  </Step>
  <Step title="N'écrire que dans /home/container">
    **Le reste du système de fichiers est en lecture seule.** Voir plus bas — c'est le
    point qui surprend le plus.
  </Step>
  <Step title="Sortir proprement sur SIGTERM">
    C'est ce que `ark stop` et `ark restart` envoient. Le conteneur est tué 30 secondes
    plus tard s'il n'a pas rendu la main.
  </Step>
</Steps>

## Le système de fichiers est en lecture seule

C'est la contrainte la moins visible et la plus fréquente. Le conteneur démarre avec
sa racine en lecture seule. Deux endroits échappent à la règle :

| Chemin | Écriture | Persistance |
| --- | --- | --- |
| `/home/container` | oui | **gardé** — survit aux redémarrages et aux déploiements |
| `/tmp` | oui | perdu au redémarrage, **100 Mo** au maximum |
| tout le reste | non | — |

Ça vaut pour ce que ton code écrit, mais aussi pour ce que tes outils écrivent sans te
le dire : caches de framework, fichiers de session, journaux, sockets.

<Warning>
  Un `EROFS: read-only file system` dans la console vient de là, presque toujours. Un
  `EACCES` aussi, quand le chemin visé appartient à root.
</Warning>

Redirige ce qui doit s'écrire :

```docker
ENV NEXT_CACHE_DIR=/home/container/.cache
ENV NPM_CONFIG_CACHE=/home/container/.npm
ENV XDG_CACHE_HOME=/home/container/.cache
ENV HOME=/home/container
```

`HOME` n'est pas posé par le nœud : beaucoup d'outils tombent sur `/` et échouent en
écriture. Le poser explicitement règle une famille entière de pannes.

## Le conteneur ne tourne pas en root

Il tourne en **uid 988, gid 988**, quel que soit le `USER` de ton image. Deux
conséquences :

- Les fichiers que tu copies dans l'image doivent être **lisibles par tous**. Un
  `COPY` depuis un poste où ils sont en `600` donne un `permission denied` au
  démarrage.
- Tu ne peux ni installer de paquet ni écrire dans `/etc` au démarrage. Tout ce qui
  demande root se fait **à la construction**, pas à l'exécution.

```docker
COPY --chmod=755 scripts/ ./scripts/
```

## L'architecture

`ark build` demande à Arkya sur quelle architecture tourne le nœud et construit pour
elle. Sur un Mac Apple Silicon, ça veut dire une construction croisée vers
`linux/amd64` — plus lente, mais juste.

```bash
ark build                          # l'architecture du nœud
ark build --platform linux/arm64   # forcer
```

Un `exec format error` dans la console veut dire que l'image a été construite pour une
autre architecture.

## Un Dockerfile qui marche

```docker Dockerfile
FROM node:22-slim

ENV HOME=/home/container
ENV NPM_CONFIG_CACHE=/home/container/.npm
ENV NODE_ENV=production

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY --chmod=755 . .

CMD ["node", "server.js"]
```

Rien n'y est propre à Arkya : c'est un Dockerfile ordinaire, qui se contente de ne pas
écrire hors de `/home/container` à l'exécution et de poser `HOME`.

<Note>
  Le `WORKDIR` de ton image est respecté — le nœud ne te force pas dans
  `/home/container`. Mais ce dossier reste le seul endroit **persistant** : une base
  SQLite, des fichiers téléversés ou un dossier de sessions doivent y vivre, pas dans
  `/app`.
</Note>

## La commande de démarrage

Sans rien préciser, le nœud lance l'`ENTRYPOINT` et le `CMD` de ton image. Pour lancer
autre chose — une migration avant le serveur, par exemple — déclare-le dans le
manifeste :

```json arkya.json
{
  "app": "mon-api",
  "start": "./scripts/migrate-then-start.sh"
}
```

Le script doit être exécutable (`--chmod=755` à la copie) et se terminer par le
processus qui tient le conteneur en vie — un `exec` final, pas un lancement en arrière-plan.

```bash scripts/migrate-then-start.sh
#!/bin/sh
set -e
npm run migrate
exec node server.js
```

<Card title="Le manifeste" icon="file-code" href="/cli/manifeste">
  `start`, la taille, les prérequis : tout ce qui se déclare autour de l'image.
</Card>

## Ce que le nœud fait, et ne fait pas

| | |
| --- | --- |
| Relance le conteneur qui sort en erreur | oui, et il cesse d'insister s'il replante dans la minute |
| Sonde de santé sur une URL | **non** — un processus qui répond en 500 sans planter reste en ligne |
| Limite la mémoire | oui, à la taille du service — au-delà, le noyau tue le processus |
| Publie un port | un seul, celui de `$PORT` |

## Quand ça ne démarre pas

<AccordionGroup>
  <Accordion title="« EROFS: read-only file system »">
    Ton code ou un de tes outils écrit hors de `/home/container` et `/tmp`. Pose `HOME`
    et les variables de cache vers `/home/container`, comme plus haut.
  </Accordion>
  <Accordion title="« permission denied » sur un fichier de l'image">
    Le conteneur tourne en uid 988 et tes fichiers ne lui sont pas lisibles. Recopie-les
    avec `COPY --chmod=755` (scripts) ou `--chmod=644` (données).
  </Accordion>
  <Accordion title="« exec format error »">
    L'image vise une autre architecture. Reconstruis sans `--platform` et laisse
    `ark build` choisir.
  </Accordion>
  <Accordion title="Le service tourne mais rien ne répond">
    L'application n'écoute pas sur `$PORT`, ou elle écoute sur `127.0.0.1`. Les deux
    donnent exactement le même symptôme. `ark logs --follow` montre sur quel port elle
    s'est liée.
  </Accordion>
  <Accordion title="Le conteneur redémarre en boucle">
    Le processus sort tout de suite. `ark console` montre les dernières lignes avant la
    sortie, et le nœud finit par dire « Aborting automatic restart, last crash occurred
    less than 60 seconds ago ».
  </Accordion>
  <Accordion title="Le processus est tué sans message">
    La mémoire du service est saturée. `ark info` donne la consommation,
    `ark scale --ram 4` agrandit.
  </Accordion>
</AccordionGroup>
