Nodefony
Framework Node.js fullstack — HTTP & WebSocket, même contexte

Booter Nodefony en Docker / Kubernetes (cloud-native)

stablemis à jour 2026-09-01

📍 DocumentationGuidesDocker & cloud-native

Le modèle — un process au premier plan, et l’orchestrateur au-dessus#

Un conteneur n’est pas une machine : c’est un process, et l’orchestrateur ne connaît que celui-là. Tout le reste — redémarrage, arrêt, sondes de vie, montée en charge — se décide sur ce process unique. D’où le principe qui gouverne toute cette page : Nodefony démarre au premier plan, reçoit les signaux directement, et ne se met jamais en arrière-plan.

C’est aussi pourquoi la mise à l’échelle ne se fait pas dans le conteneur mais au-dessus : un process par pod, et l’orchestrateur en lance autant qu’il faut.

Règle d’or : au premier plan, jamais détaché#

En Docker et Kubernetes, le process Node démarre au premier plan. Jamais en arrière-plan (&), jamais de mise en démon (PM2 a été retiré du framework).

Pourquoi : l’orchestrateur (Docker, k8s, systemd) pilote le cycle de vie du container via ce process foreground :

nodefony production est déjà foreground : le Kernel boote, les serveurs HTTP/WS écoutent (ce sont leurs handles qui gardent le process vivant), aucune daemonisation.

Ce que ça donne, mesuré#

nodefony production --workers 1 démarre au premier plan, serveurs à l’écoute. Un SIGTERM — c’est ce qu’envoient docker stop et l’arrêt d’un pod — déclenche l’arrêt gracieux : serveurs HTTP et HTTPS fermés proprement, sortie en code 0, en quelques centaines de millisecondes. Aucun SIGKILL n’est nécessaire.

Les quatre signaux d’arrêt sont attachés au même endroit (Cli.ts:367), et un second signal pendant l’arrêt coupe court plutôt que d’attendre (Cli.ts:348) — c’est ce qui évite un pod bloqué en fin de vie.

Trois régimes de boot, trois objets Docker/k8s#

Mode Commande Type Objet k8s Garde le process vivant via
Serveur nodefony production SERVER Deployment + Service handles des serveurs HTTP/WS
Batch one-shot nodefony <module>:<cmd> (CONSOLE) CONSOLE Job / CronJob — (exécute puis exit 0)
Daemon (worker queue, consumer, agent) commande CONSOLE qui park CONSOLE Deployment (sans Service) un handle explicite (socket/consumer/timer)

⚠️ Daemon CONSOLE : un await new Promise(() => {}) ne suffit PAS à garder Node vivant (une Promise pending n’est pas un handle d’event loop — Node sort dès l’event loop vide). Un vrai daemon tient un handle actif (consumer Kafka/Redis, socket, timer). Vérifié en test.

Topologie : 1 process = 1 pod (défaut) vs cluster#

Logs#

Vers stdout/stderr (collectés par Docker → driver de logs / collecteur centralisé). Ne pas écrire dans des fichiers depuis le container (éphémère). Le Log Backplane (drivers loki/ opensearch) pousse vers un backend externe en prod.

Dockerfile de référence (multi-stage)#

# ---- build ----
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build          # rolldown : dist/ de tous les espaces de travail

# ---- runtime ----
FROM node:24-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/src ./src
COPY --from=build /app/package*.json ./
# 1 process = 1 container, FOREGROUND. SIGTERM → graceful shutdown.
# Forme exec (pas de shell) → le process Node reçoit directement les signaux.
EXPOSE 5151 5152
CMD ["npx", "nodefony", "production"]
# Cluster intra-container (VPS multi-cœurs) : CMD ["npx","nodefony","cluster","-w","4"]

Notes :

Kubernetes — probes & timeouts#

Le framework expose deux sondes, actives par défaut — rien à écrire :

Sonde Chemin Ce qu’elle répond
Vivacité /livez 200 tant que le process sert — y compris pendant l’arrêt : un pod qui vide ses connexions n’est pas un pod mort, le tuer couperait les requêtes en cours
Disponibilité /readyz 200 quand le pod peut recevoir du trafic, 503 dès le début de l’arrêt — l’équilibreur le retire avant que la première connexion tombe

Elles sont montées avant la limitation de débit (http-kernel.ts:967) : un kubelet qui sonde toutes les secondes ne doit pas se faire limiter, puis déclarer le pod mort. Les chemins et l’activation se règlent dans la configuration du module http (config.ts, enabled à true par défaut).

livenessProbe:
  { httpGet: { path: /livez, port: 5151 }, initialDelaySeconds: 10 }
readinessProbe: { httpGet: { path: /readyz, port: 5151 }, periodSeconds: 5 }
terminationGracePeriodSeconds: 30 # > durée du graceful shutdown (~mesurée < 1 s)

📖 Lexique#

Terme Ce que c’est
Premier plan (foreground) Le process reste le process principal du conteneur. C’est lui que l’orchestrateur surveille et signale.
Arrêt gracieux Fermer les serveurs, laisser finir les requêtes en cours, puis sortir — au lieu d’être tué. Déclenché par SIGTERM.
Sonde de vivacité (liveness) Ce qui dit « ce pod est-il encore vivant ? ». S’il échoue, le pod est redémarré.
Sonde de disponibilité (readiness) Ce qui dit « ce pod peut-il recevoir du trafic ? ». S’il échoue, il est retiré du service sans être tué.
Forme exec CMD ["a", "b"] plutôt que CMD a b : sans shell intermédiaire, donc les signaux atteignent Node.
Init Un petit process parent (--init, tini) qui récolte les process orphelins et relaie les signaux.

⚠️ Pièges#

🧪 Tests & couverture#

Les chiffres exacts vivent dans la carte de l’aperçu, régénérée en comptant — jamais figés ici.

<!-- prettier-ignore -->

Type Ce qui est prouvé
Unitaires (signaux, CLI) nodefony Cli.test.ts les signaux attachés, le second signal qui coupe court, les codes de sortie
Unitaires (cluster) nodefony ClusterManager.test.ts, ClusterProbeAggregator.test.ts, ClusterRelay.test.ts la supervision des ouvriers, l’agrégation des sondes, le relais entre process
Intégration (CLI réelle) nodefony CliIntegration.test.ts, detachedStart.test.ts le démarrage effectif, et ce que le détachement change

Ce que ces tests ne couvrent pas : le comportement d’un orchestrateur réel. Le contrat « SIGTERM → arrêt propre » est vérifié sur le process ; ce que Kubernetes en fait dépend de vos terminationGracePeriodSeconds et de vos sondes.

🔗 Pour aller plus loin#