ENTRYPOINT y no son dos formas redundantes de escribir “qué ejecutar”. Juntas definen cómo la imagen propone su proceso principal y qué parte de ese comando puede reemplazar el usuario al crear un contenedor.
CMD
docker
ENTRYPOINT ["node"]
CMD ["server.js"]
La configuración anterior produce por defecto:
Texto
node server.js
La decisión importa porque el proceso resultante se convierte normalmente en PID 1 dentro del contenedor. Debe recibir señales, terminar de forma ordenada, propagar errores y administrar procesos hijos.
Una imagen puede construir y arrancar correctamente durante pruebas simples, pero fallar durante un deployment si su shell, script o proceso principal no maneja estas responsabilidades.
La forma JSON define argumentos directamente, sin insertar un shell implícito.
Beneficios:
cada argumento conserva sus límites;
no existe expansión accidental del shell;
el ejecutable puede convertirse directamente en PID 1;
las señales llegan a ese proceso;
paths con espacios no dependen de quoting del shell;
el comportamiento es más predecible.
La forma exec no expande variables automáticamente:
docker
ENV APP_FILE=server.js
CMD ["node", "$APP_FILE"]
Node recibe literalmente $APP_FILE. Para expansión, la aplicación debe leer la variable, el command debe usar un shell conscientemente o un script debe construir los argumentos.
Docker utiliza el shell predeterminado de la plataforma, conceptualmente:
Texto
/bin/sh -c "node server.js"
La shell form permite:
expansión de variables;
pipes;
redirecciones;
&& y ||;
globbing.
Pero para el proceso principal introduce una capa:
Texto
PID 1 → /bin/sh -c
└── node server.js
El shell puede no reenviar señales de la forma esperada y Node deja de ser PID 1. Durante docker stop, SIGTERM puede llegar al shell mientras la aplicación continúa hasta que Docker fuerza SIGKILL.
Para runtime, prefiere exec form salvo que el shell sea una parte intencional y correctamente administrada del diseño.
Algunas señales cuya acción predeterminada terminaría un proceso normal pueden tener tratamiento diferente para PID 1 si no existe un handler. La aplicación debe registrar handlers cuando necesita graceful shutdown.
Cuando un proceso hijo termina, su padre debe recolectar su estado mediante wait. Si el padre desaparece, procesos huérfanos pueden ser adoptados por PID 1. Una aplicación que crea muchos hijos y no los recolecta puede acumular zombies.
sin exec deja el shell como PID 1 y la aplicación como hijo.
Otro error:
Bash
node dist/server.js &wait
puede ser válido solo si el script implementa forwarding de señales, waits y manejo de múltiples procesos correctamente. Para una aplicación simple, exec es más seguro.
No es una corrección permanente. Si producción siempre necesita sobrescribirlo, el diseño de la imagen probablemente no representa bien su responsabilidad.
--entrypoint recibe el ejecutable; los argumentos adicionales se escriben después de la imagen:
Si la aplicación no administra procesos hijos correctamente:
Bash
docker run --init my-api
Docker inserta un init mínimo como PID 1 que reenvía señales y recolecta hijos.
Es útil para:
aplicaciones que lanzan subprocesses;
herramientas que no fueron diseñadas para PID 1;
tests y browsers headless.
No reemplaza graceful shutdown de la aplicación. El init puede reenviar SIGTERM, pero la app todavía debe reaccionar.
También pueden utilizarse init systems dentro de la imagen, pero añadir un supervisor completo requiere justificar múltiples procesos y políticas de reinicio.
“Un proceso por contenedor” es una guía, no una ley del kernel. La idea real es mantener una responsabilidad operativa clara.
Puede ser válido que una aplicación cree hijos o que un container especializado ejecute varios procesos estrechamente acoplados. Sin embargo, debes resolver:
quién es PID 1;
cómo se propagan señales;
cómo se reinicia cada componente;
cómo se agregan logs;
qué significa health;
qué ocurre si uno falla;
cómo se actualizan.
Ejecutar API, database y cron en un solo contenedor suele mezclar lifecycle y estado que deberían administrarse por separado.
Aquí no se fija ENTRYPOINT porque la imagen contiene varios commands legítimos. El runtime reemplaza CMD de forma directa.
Una alternativa con entrypoint puede funcionar, pero debe evitar convertir todos los argumentos en parámetros obligatorios de Node si necesitas ejecutar shell u otras herramientas.
La sintaxis corta como string puede introducir diferencias de shell. Para claridad, usa listas cuando necesitas argv explícito y verifica la configuración final con:
Varias réplicas pueden ejecutar migraciones concurrentes. Las migraciones necesitan coordinación, idempotencia y un lifecycle separado cuando el riesgo lo exige.
Cache de build y BuildKit, para comprender cómo el builder calcula reutilización, qué inputs invalidan cada paso y cómo acelerar sin sacrificar corrección.