Docker Compose: diferencia entre .env, env_file y secrets
Aclara la diferencia entre .env, env_file y environment en Docker Compose, su precedencia y por qué las contraseñas deben ir en secrets, no en variables.
Las tres cosas que se suelen llamar archivo de entorno
Docker Compose tiene tres mecanismos distintos con nombres muy parecidos. El archivo .env rellena los marcadores de posición ${VARIABLE} dentro del propio archivo compose.yaml, antes de que Compose analice siquiera el archivo. El atributo env_file: carga un archivo de pares clave/valor en el entorno del contenedor. El atributo environment: establece directamente variables en el contenedor y se escribe en el archivo Compose. No son intercambiables. Cuando dos de ellos establecen la misma clave, el resultado se determina mediante un orden de precedencia documentado.
Esta guía muestra cada mecanismo en funcionamiento y demuestra la precedencia con un comando que puede ejecutar. Después aborda el aspecto más importante: cualquier persona que pueda ejecutar docker inspect puede leer las variables de entorno, por lo que las contraseñas no deben almacenarse en ellas. Si todavía no conoce los archivos Compose, empiece por Conceptos básicos de Docker Compose en un VPS y vuelva aquí para consultar la configuración.
El archivo .env es para el archivo de Compose, no para el contenedor
Cree un directorio y coloque dos archivos en él.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGAhora pida a Compose que muestre lo que ha interpretado realmente.
docker compose configLa salida muestra image: alpine:3.20. El marcador de posición ha desaparecido porque la interpolación se realizó durante el análisis. Compose busca .env en el directorio del proyecto, que es el directorio que contiene el archivo de Compose, y sustituye cada ${NAME} que encuentra.
A continuación, inicie el servicio.
docker compose run --rm demoprintenv ALPINE_TAG termina con el estado 1 y no muestra ninguna salida. La variable no existe dentro del contenedor. Este es el malentendido más común: .env configuró el archivo de Compose, no el proceso. Un archivo .env que contenga POSTGRES_PASSWORD=hunter2 no hace nada por su base de datos, a menos que alguna parte del archivo de Compose haga referencia a él.
${NAME:-default} proporciona un valor alternativo cuando la variable no está definida o está vacía. ${NAME:?message} hace que Compose rechace el inicio y muestre su mensaje. Esta es la opción adecuada para un valor que no tiene un valor predeterminado seguro.
env_file carga variables en el contenedor
El atributo env_file: especifica uno o varios archivos cuyo contenido se convierte en variables de entorno del contenedor.
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoEsto muestra from_env_file. El formato del archivo consiste en líneas de KEY=value simples, una por línea, y # al principio indica un comentario. No es sintaxis de shell. En la mayoría de los casos, las comillas se conservan como parte del valor y no es necesario usar prefijos export. No escriba espacios alrededor del signo =, porque KEY = value crea una variable llamada literalmente KEY con un espacio inicial en su valor.
Una ruta env_file inexistente provoca un error y Compose se detiene. Márquela como opcional si el archivo puede no existir legítimamente:
env_file:
- path: ./app.env
required: falseenvironment define variables en línea
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentSe aceptan dos sintaxis: el formato de asignación mostrado arriba y un formato de lista que usa - GREETING=from_environment. Su comportamiento es idéntico. El formato de lista tiene un recurso adicional: una clave sin valor transmite la variable desde el shell donde ejecutó docker compose.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoEsto muestra from_my_shell. Si lo ejecuta sin definir GREETING en el shell, Compose no establece nada y no muestra ninguna advertencia. Conviene conocer estos fallos de transmisión silenciosos, porque un servicio que se inicia con una variable de contraseña vacía suele arrancar correctamente y queda completamente expuesto.
Cuál prevalece
Docker documenta el orden de precedencia, de mayor a menor: docker compose run -e en la línea de comandos, después environment o env_file, cuyo valor se interpola desde el shell o desde un archivo de entorno; luego environment sin interpolación en el archivo compose; después env_file; y, por último, la directiva ENV incorporada en la imagen.
La versión breve para el trabajo diario: environment: prevalece sobre env_file:, y -e en la línea de comandos prevalece sobre ambos. Puede comprobarlo en un solo archivo.
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETINGEl primero imprime from_environment, por lo que environment: sobrescribió el valor de app.env. El segundo imprime from_cli. Nada del archivo compose sobrescribe la línea de comandos.
Cuando un contenedor se comporta como si la configuración no se hubiera aplicado, no lo suponga. docker compose config imprime el archivo completamente resuelto y docker compose config --environment imprime las variables de interpolación que Compose está utilizando. La mayoría de los informes de que «se ignora el archivo de entorno» se deben a que un valor está definido dos veces en niveles diferentes.
Por qué se filtran las variables de entorno
Establezca una contraseña en environment: y se almacenará en la configuración del contenedor en disco. Cualquier usuario del grupo docker podrá verla.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'La salida contiene "DB_PASSWORD=hunter2" en texto sin formato. Otras tres rutas exponen el mismo valor. docker compose config lo muestra en el terminal, y así puede terminar pegado en un foro de soporte. Cualquier proceso dentro del contenedor puede leer /proc/1/environ, y todos los procesos secundarios heredan la variable. Además, los controladores de errores de las aplicaciones suelen volcar todo el entorno en un registro o en un informe de errores.
La pertenencia al grupo docker equivale en la práctica a tener root en el host. Por tanto, no puede tratarla como un límite de privilegios. La guía sobre cuentas de usuario con privilegios mínimos en un VPS explica por qué conviene restringir ese grupo en cualquier sistema compartido.
Los secretos de Compose mantienen el valor en un archivo
Compose admite secretos basados en archivos. El valor se monta en el contenedor como un archivo, en lugar de inyectarse en el entorno.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtEl secreto se monta en /run/secrets/db_password dentro del contenedor. El nombre que aparece después de la barra es el nombre del secreto definido en el bloque de nivel superior secrets:.
El sufijo _FILE es una convención que usan las Docker Official Images, incluidas postgres, mysql y mariadb. Sus scripts de entrada comprueban si existe VARNAME_FILE, leen el archivo y utilizan su contenido. No es una función de Docker, por lo que sólo funciona cuando la imagen la implementa. Consulte la documentación de la imagen antes de asumir que se respetará SOMETHING_FILE. Las aplicaciones que no admiten esta convención suelen poder leer el archivo durante el arranque, o puede pasarles la ruta y hacer que su propio entrypoint se encargue de ello.
Verifique el resultado desde dentro del contenedor en ejecución:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDEl primero muestra la contraseña. El segundo no muestra nada, porque el valor nunca entró en el entorno. Ese es el objetivo: docker inspect en este contenedor sólo muestra la ruta no confidencial.
Proteja el archivo de origen en el host, porque el secreto sólo es tan privado como el archivo que lo contiene:
chmod 600 db_password.txtEl término medio práctico en un VPS
Muchas imágenes autoalojadas no admiten variables _FILE, por lo que las variables de entorno son la única forma de proporcionarles valores. En un VPS administrado por una sola persona, el objetivo realista es evitar que los valores queden en un archivo legible por todos dentro del directorio del proyecto y mantenerlos fuera de git.
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 crea el archivo con los permisos ya establecidos, por lo que no existe un intervalo durante el cual todos puedan leerlo. root es su propietario, así que un usuario sin privilegios en el equipo no puede leerlo. Sin embargo, cualquiera que pueda ejecutar docker todavía puede extraer el valor del contenedor. Añada *.env y .env a .gitignore y confirme un app.env.example que contenga los nombres de las claves con valores vacíos. Una contraseña confirmada en el repositorio es una contraseña que debe rotarse.
Rotar un valor implica reiniciar el servicio. Las variables de entorno se leen una sola vez cuando se inicia el proceso del contenedor, por lo que editar el archivo no cambia nada hasta ejecutar docker compose up -d --force-recreate db. Este es el mismo patrón que se usa en la guía n8n detrás de HTTPS en un VPS, donde la clave de cifrado se mantiene fuera del archivo compose.
Dividir la configuración por entorno
Compose lee .env de forma predeterminada desde el directorio del proyecto. Use --env-file para indicar otra ubicación.
docker compose --env-file .env.staging configLos archivos se leen en orden y los archivos posteriores sobrescriben a los anteriores. Mantenga los valores predeterminados que no sean secretos en un archivo versionado y los secretos en un archivo que nunca salga del servidor. Lo mismo se aplica a env_file:: cuando una clave está duplicada, prevalece el último archivo de la lista.
FAQ
¿Por qué se ignora mi archivo .env dentro del contenedor?
No se ignora. El archivo .env sólo sustituye los marcadores ${NAME} del archivo compose. Nunca establece variables dentro de un contenedor. Para introducir el valor en el contenedor, haga referencia a él: environment: { KEY: "${NAME}" }, o use env_file: ./that-file.env en su lugar.
¿Tiene prioridad environment sobre env_file o al contrario?
environment: tiene prioridad. El orden documentado de Docker coloca el atributo environment por encima del atributo env_file, y ambos están por debajo de docker compose run -e en la línea de comandos. Si una clave está definida en ambos lugares, el valor de env_file se ignora silenciosamente.
¿Cómo puedo ver el valor final que usará Compose?
Ejecute docker compose config para mostrar el archivo compose completamente resuelto, con toda la interpolación aplicada. Para un contenedor que ya está en ejecución, docker inspect <container> --format '{{json .Config.Env}}' muestra exactamente lo que recibió su proceso.
¿Los secretos de Compose están cifrados?
No. Un secreto basado en archivo se monta en el contenedor como un archivo de texto sin formato en /run/secrets/<name>, y el archivo de origen permanece sin cifrar en el disco del host. La ventaja es el alcance, no el cifrado: el valor no aparece en el entorno del contenedor, ni en la salida de docker inspect, ni en los volcados de memoria por fallo que imprimen el entorno.
¿Puedo usar comillas y espacios en un archivo de entorno?
Use KEY=value with spaces y omita las comillas. Compose trata todo el resto de la línea como el valor, por lo que las comillas suelen quedar como caracteres literales dentro del valor. Nunca ponga espacios alrededor de =, porque la clave conservará un espacio final y no coincidirá con nada.