Saltar al contenido principal

Configuración y variables de entorno

Una imagen representa una versión de la aplicación. Sin embargo, el mensaje, el modo de ejecución o el nombre de otro servicio pueden cambiar entre entornos.

misma imagen
├── desarrollo: configuración A
├── pruebas: configuración B
└── otro entorno: configuración C

No queremos construir tastematch-dev, tastematch-test y tastematch-prod si lo único que cambia es configuración que puede proporcionarse al ejecutar.

El problema de escribir configuración en el código​

Imagina estos valores dentro de app.js:

const mode = "development";
const databaseHost = "localhost";

Para cambiar de entorno tendríamos que editar el código y reconstruir la imagen. Además, localhost sería incorrecto si la base de datos vive en otro contenedor.

La aplicación debería poder recibir esos valores sin cambiar su código ni su imagen.

Qué es una variable de entorno​

Una variable de entorno es un valor con nombre que un proceso puede consultar durante su ejecución.

proceso de TasteMatch
└── entorno
├── APP_MODE=development
├── WELCOME_MESSAGE=Hola
└── DB_HOST=tastematch-db

La variable no modifica automáticamente la aplicación: el código debe leerla y decidir qué hacer.

Proporcionar una variable con -e​

Usa la imagen de TasteMatch y proporciona un valor al crear el contenedor:

docker run -d --name tastematch-dev -p 3000:3000 \
-e APP_MODE=development \
tastematch:3.0

-e es la forma corta de --env:

-e APP_MODE=development
│ └── valor
└─────────── nombre

Podemos proporcionar varias variables:

docker run -d --name tastematch-test -p 3001:3000 \
--env APP_MODE=test \
--env WELCOME_MESSAGE="TasteMatch en pruebas" \
tastematch:3.0

Los dos contenedores parten de la misma imagen y reciben configuraciones distintas.

Comprobar qué recibió el contenedor​

Para una comprobación puntual podemos ejecutar printenv dentro de un contenedor que ya está en marcha:

docker exec tastematch-dev printenv APP_MODE
docker exec tastematch-test printenv WELCOME_MESSAGE

docker exec ejecuta un comando dentro de un contenedor existente; no crea otro, a diferencia de docker run. En la siguiente unidad lo utilizaremos como herramienta de diagnóstico. No muestres todas las variables sin pensar: podrían incluir información sensible.

También puedes inspeccionar la configuración del contenedor con:

docker inspect tastematch-dev

La inspección puede mostrar variables y otros datos de configuración. Si existen datos sensibles, no compartas su salida completa sin revisarla.

La aplicación debe leer la variable​

Evoluciona el inicio de app.js:

const express = require("express");

const app = express();
const port = 3000;
const mode = process.env.APP_MODE || "development";
const message = process.env.WELCOME_MESSAGE || "TasteMatch preparado";

app.get("/", (_request, response) => {
response.send(`${message} (${mode})`);
});

app.listen(port, "0.0.0.0", () => {
console.log(`TasteMatch escucha en el puerto ${port} en modo ${mode}`);
});

|| proporciona un valor por defecto cuando la variable no está definida. Antes de construir TasteMatch 4.0, sustituye también la instrucción ENV del modo de ejecución en el Dockerfile por:

ENV APP_MODE=production

Después de modificar el código y el Dockerfile, construye una imagen nueva:

docker build -t tastematch:4.0 .

Antes de ejecutar las nuevas configuraciones, retira únicamente los dos contenedores anteriores para liberar los puertos del host 3000 y 3001:

docker stop tastematch-dev tastematch-test
docker rm tastematch-dev tastematch-test
docker ps

Comprueba que ya no aparecen sus publicaciones en 3000 y 3001. Si otro servicio de tu equipo ocupa alguno de esos puertos, resuelve esa ocupación antes de continuar; no elimines recursos ajenos a la práctica.

Ahora ejecuta dos configuraciones:

docker run -d --name taste-a -p 3000:3000 \
-e APP_MODE=development \
-e WELCOME_MESSAGE="Hola desde desarrollo" \
tastematch:4.0

docker run -d --name taste-b -p 3001:3000 \
-e APP_MODE=test \
-e WELCOME_MESSAGE="Hola desde pruebas" \
tastematch:4.0

Compara http://localhost:3000 y http://localhost:3001. Misma imagen, comportamiento configurado en cada ejecución.

Configurar el nombre de otro servicio​

Este es un ejemplo conceptual: si una aplicación comparte una red adecuada con otro servicio, podemos proporcionarle su nombre mediante una variable como DB_HOST=tastematch-db, en lugar de usar localhost.

DB_HOST=tastematch-db
↓ resolución por nombre
red compartida
↓
contenedor tastematch-db

La variable no crea la red ni el otro contenedor, ni los conecta. La aplicación debe leer DB_HOST y ambos contenedores deben estar correctamente conectados a una red que permita resolver ese nombre.

Varias variables con --env-file​

Para evitar un comando largo, crea development.env:

APP_MODE=development
WELCOME_MESSAGE=TasteMatch local
DB_HOST=tastematch-db

Y úsalo explícitamente:

docker run -d --name taste-env -p 3002:3000 \
--env-file development.env \
tastematch:4.0

--env-file lee pares NOMBRE=valor del archivo indicado. El archivo no necesita llamarse .env; ese nombre es una convención frecuente, no un comportamiento mágico de Docker.

Un archivo llamado .env tampoco se aplica por existir junto al Dockerfile. Debes pasarlo con --env-file cuando quieras que docker run lo utilice.

Recuperar ENV del Dockerfile​

En DK1-02 vimos ENV. En el Dockerfile de TasteMatch 4.0 hemos configurado:

ENV APP_MODE=production

Ese valor forma parte de la configuración base de la imagen. Un valor proporcionado al ejecutar puede sustituirlo para ese contenedor:

docker run --rm -e APP_MODE=test tastematch:4.0 printenv APP_MODE
ENV APP_MODE=production → valor base de la imagen
-e APP_MODE=test → valor utilizado por ese contenedor

No necesitamos una imagen diferente solo para cambiar APP_MODE.

Valores opcionales y obligatorios​

Un valor opcional puede tener un predeterminado:

const mode = process.env.APP_MODE || "development";

Para probar una variable obligatoria sin modificar TasteMatch 4.0, copia tu carpeta tastematch como tastematch-db-prueba. En el app.js de esa copia, añade este bloque al principio, antes del código que inicia el servidor:

const databaseHost = process.env.DB_HOST;

if (!databaseHost) {
console.error("Falta la variable obligatoria DB_HOST");
process.exit(1);
}

La copia conserva el resto de app.js, package.json, Dockerfile y .dockerignore. Desde tastematch-db-prueba, construye una imagen de prueba e inicia un contenedor sin proporcionar DB_HOST:

docker build -t tastematch:config-obligatoria .
docker run --name tastematch-sin-db tastematch:config-obligatoria

El mensaje Falta la variable obligatoria DB_HOST y la salida con código 1 son el resultado esperado. No necesitas una base de datos: solo estamos comprobando que la aplicación detecta la configuración ausente. El proceso principal termina y el contenedor queda detenido. Comprueba el estado Exited y consulta los logs:

docker ps -a
docker logs tastematch-sin-db

Eso es mejor que continuar con un valor ambiguo y fallar lejos de la causa.

Después de guardar la evidencia, elimina únicamente este contenedor detenido y su imagen de prueba:

docker rm tastematch-sin-db
docker image rm tastematch:config-obligatoria

Configuración no es lo mismo que secreto​

Valores como un modo de ejecución, puerto o hostname suelen ser configuración no sensible. Contraseñas, tokens y claves privadas son sensibles.

No incrustes secretos:

# Incorrecto
ENV DATABASE_PASSWORD=secreto

Tampoco asumas que --env-file cifra el contenido. Es texto que Docker entrega al contenedor. Añadir el archivo a .gitignore ayuda a no incorporarlo por accidente en commits futuros, pero:

  • no cifra el archivo;
  • no controla quién puede leerlo en el equipo;
  • no elimina un secreto ya versionado;
  • no sustituye una gestión profesional de secretos.

En este curso utiliza únicamente valores ficticios y no sensibles.

Errores habituales​

  • Construir otra imagen para cada valor: reutiliza la misma imagen cuando solo cambia configuración de ejecución.
  • Guardar credenciales en el Dockerfile: quedan incorporadas al diseño de la imagen.
  • Subir .env con datos sensibles: revisa antes de versionar y nunca uses secretos reales en prácticas.
  • Creer que .gitignore borra el historial: no retira valores ya confirmados en commits anteriores.
  • Creer que --env-file cifra: solo carga pares de texto.
  • Definir una variable que la aplicación no lee: no cambiará su comportamiento.
  • Confundir entorno del host y del contenedor: proporciona explícitamente lo que necesite el proceso.

Cerrar la práctica guiada​

taste-a, taste-b y taste-env utilizan respectivamente los puertos del host 3000, 3001 y 3002. Cuando termines de comparar su configuración, retíralos para liberar esos puertos antes del reto:

docker stop taste-a taste-b taste-env
docker rm taste-a taste-b taste-env

Conserva la imagen tastematch:4.0 y los archivos de la aplicación.

Mini reto: una imagen, tres ejecuciones​

Adapta una aplicación pequeña para leer:

  • APP_MODE, con valor por defecto development;
  • WELCOME_MESSAGE, con valor por defecto;
  • SERVICE_HOST, obligatoria.

Después:

  1. construye una sola imagen config-reto:1.0;
  2. ejecútala con variables individuales mediante -e;
  3. ejecútala con otra configuración mediante --env-file;
  4. comprueba dentro del contenedor dos valores concretos;
  5. inicia una ejecución sin SERVICE_HOST;
  6. usa estado y logs para explicar por qué terminó;
  7. conecta una ejecución a una red y usa como hostname el nombre de otro servicio;
  8. demuestra que no reconstruiste la imagen entre configuraciones;
  9. identifica qué valores serían sensibles y no los incluyas;
  10. retira únicamente los contenedores del reto.

Entrega la configuración de ejemplo sin secretos, los comandos y una comparación:

misma imagen + configuración A → contenedor A
misma imagen + configuración B → contenedor B

En la próxima unidad utilizaremos estado, logs, exec e inspect para investigar sistemáticamente configuraciones incorrectas y otros fallos.