Solución:
Las imágenes son inmutables
Dockerfile define el proceso de construcción de una imagen. Una vez construida, la imagen es inmutable (no se puede cambiar). Las variables de tiempo de ejecución no son algo que se incorpore a esta imagen inmutable. Entonces, Dockerfile es el lugar equivocado para abordar esto.
Usando un script de punto de entrada
Lo que probablemente quiera hacer es anular el valor predeterminado ENTRYPOINT
con su propia secuencia de comandos y haga que esa secuencia de comandos haga algo con las variables de entorno. Dado que el script de punto de entrada se ejecutaría en tiempo de ejecución (cuando se inicia el contenedor), este es el momento correcto para recopilar variables de entorno y hacer algo con ellas.
Primero, necesita ajustar su Dockerfile para conocer un script de punto de entrada. Si bien Dockerfile no está directamente involucrado en el manejo de la variable de entorno, aún necesita conocer este script, porque el script se integrará en su imagen.
Dockerfile:
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "start"]
Ahora, escriba un script de punto de entrada que realice la configuración necesaria antes de se ejecuta el comando, y al final, exec
el comando en sí.
entrypoint.sh:
#!/bin/sh
# Where $ENVSUBS is whatever command you are looking to run
$ENVSUBS < fil1 > file2
npm install
# This will exec the CMD from your Dockerfile, i.e. "npm start"
exec "[email protected]"
Aquí, he incluido npm install
, ya que preguntaste sobre esto en los comentarios. Notaré que esto se ejecutará npm install
en cada carrera. Si eso es apropiado, está bien, pero quería señalar que se ejecutará siempre, lo que agregará algo de latencia a su tiempo de inicio.
Ahora reconstruya su imagen, de modo que la secuencia de comandos del punto de entrada sea parte de ella.
Usar variables de entorno en tiempo de ejecución
El script de punto de entrada sabe cómo usar la variable de entorno, pero aún debe decirle a Docker que importe la variable en tiempo de ejecución. Puedes usar el -e
bandera a docker run
para hacerlo.
docker run -e "ENVSUBS=$ENVSUBS" <image_name>
Aquí, se le dice a Docker que defina una variable de entorno ENVSUBS
, y el valor que se le asigna es el valor de $ENVSUBS
desde el entorno de shell actual.
Cómo funcionan los scripts de punto de entrada
Me extenderé un poco sobre esto, porque en los comentarios, parecía que estabas un poco confuso sobre cómo encaja esto.
Cuando Docker inicia un contenedor, ejecuta un comando (y solo uno) dentro del contenedor. Este comando se convierte en PID 1, al igual que init
o systemd
en un sistema Linux típico. Este proceso es responsable de ejecutar cualquier otro proceso que deba tener el contenedor.
Por defecto, el ENTRYPOINT
es /bin/sh -c
. Puede anularlo en Dockerfile, o docker-compose.yml, o usando el comando docker.
Cuando se inicia un contenedor, Docker ejecuta el comando entrypoint y pasa el comando (CMD
) a él como una lista de argumentos. Anteriormente, definimos nuestro propio ENTRYPOINT
como /entrypoint.sh
. Eso significa que en su caso, esto es lo que Docker ejecutará en el contenedor cuando se inicie:
/entrypoint.sh npm start
Porque ["npm", "start"]
se definió como el comando, eso es lo que se pasa como una lista de argumentos al script de punto de entrada.
Porque definimos una variable de entorno usando el -e
flag, este script de punto de entrada (y sus hijos) tendrán acceso a esa variable de entorno.
Al final del script de punto de entrada, ejecutamos exec "[email protected]"
. Porque [email protected]
se expande a la lista de argumentos pasada al script, esto se ejecutará
exec npm start
Y porqué exec
ejecuta sus argumentos como un comando, reemplazando el proceso actual consigo mismo, cuando haya terminado, npm start
se convierte en PID 1 en su contenedor.
Por qué no puede usar varios CMD
En los comentarios, preguntó si puede definir múltiples CMD
entradas para ejecutar varias cosas.
Solo puedes tener uno ENTRYPOINT
y uno CMD
definido. Estos no se utilizan en absoluto durante el proceso de compilación. diferente a RUN
y COPY
, no se ejecutan durante la compilación. Se agregan como elementos de metadatos a la imagen una vez que se crea.
Solo más tarde, cuando la imagen se ejecuta como contenedor, estos campos de metadatos se leen y se utilizan para iniciar el contenedor.
Como se mencionó anteriormente, el punto de entrada es lo que realmente se ejecuta y se pasa el CMD
como una lista de argumentos. La razón por la que están separados es en parte histórica. En las primeras versiones de Docker, CMD
era la única opción disponible, y ENTRYPOINT
fue arreglado como siendo /bin/sh -c
. Pero debido a situaciones como ésta, Docker finalmente permitió ENTRYPOINT
a definir por el usuario.
¿Se ejecutará el comando Run c cuando la variable env esté disponible?
Variables de entorno establecidas con -e
la bandera se establece cuando usted run
El contenedor.
El problema es que Dockerfile se lee en el contenedor build
, entonces el RUN
el comando será no tenga en cuenta estas variables ambientales.
La forma de establecer las variables de entorno en la compilación es agregar en su Dockerfile, ENV
línea. (https://docs.docker.com/engine/reference/builder/#/environment-replacement)
Entonces su Dockerfile puede ser:
FROM node:latest
WORKDIR /src
ADD package.json .
ENV A YOLO
RUN echo "$A"
Y la salida:
$ docker build .
Sending build context to Docker daemon 2.56 kB
Step 1 : FROM node:latest
---> f5eca816b45d
Step 2 : WORKDIR /src
---> Using cache
---> 4ede3b23756d
Step 3 : ADD package.json .
---> Using cache
---> a4671a30bfe4
Step 4 : ENV A YOLO
---> Running in 7c325474af3c
---> eeefe2c8bc47
Removing intermediate container 7c325474af3c
Step 5 : RUN echo "$A"
---> Running in 35e0d85d8ce2
YOLO
---> 78d5df7d2322
Verá en la línea anterior al último cuando el RUN
comando lanzado, el contenedor es consciente de que la variable de entorno está configurada.
Para imágenes con bash
como punto de entrada predeterminado, esto es lo que hago para permitirme ejecutar algunos scripts antes del inicio del shell si es necesario:
FROM ubuntu
COPY init.sh /root/init.sh
RUN echo 'a=(${BEFORE_SHELL//:/ }); for c in ${a[@]}; do source $x; done' >> ~/.bashrc
y si desea obtener un script en el inicio de sesión del contenedor, pase su ruta en la variable de entorno BEFORE_SHELL
. Ejemplo usando docker-compose:
version: '3'
services:
shell:
build:
context: .
environment:
BEFORE_SHELL: '/root/init.sh'
Algunas observaciones:
- Si
BEFORE_SHELL
no está configurado, entonces no pasa nada (tenemos el comportamiento predeterminado) - Puede pasar cualquier ruta de script disponible en el contenedor, incluidos los montados
- Los scripts se obtienen de modo que las variables definidas en los scripts estén disponibles en el contenedor.
- Se pueden pasar varios scripts (utilice un
:
para separar los caminos)