SSD Nodes Learn 8GB de RAM — $66/año
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-02

Docker Compose: diferencias 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 env

Docker Compose tiene tres mecanismos independientes con nombres muy parecidos. El archivo .env sustituye los marcadores de posición ${VARIABLE} dentro del propio archivo compose.yaml, antes de que Compose analice 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, escritas en el archivo de Compose. No son intercambiables. Si dos de ellos establecen la misma clave, el resultado lo determina 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 cubre 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 de 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' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

Ahora pida a Compose que muestre lo que realmente analizó.

docker compose config

La salida muestra image: alpine:3.20. El marcador de posición desapareció 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, ejecute el servicio.

docker compose run --rm demo

printenv 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 para 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 se niegue a iniciar 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 más archivos cuyo contenido se convierte en variables de entorno del contenedor.

printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

Esto imprime from_env_file. El formato del archivo consta de líneas simples de KEY=value, una por línea, y # al principio indica un comentario. No es un script de shell. En la mayoría de los casos, las comillas se conservan como parte del valor y no se necesitan prefijos export. No coloque espacios alrededor del signo =, porque KEY = value crea una variable llamada literalmente KEY , cuyo valor empieza con un espacio.

Una ruta env_file inexistente es un error y Compose se detiene. Márquela como opcional si el archivo puede no existir legítimamente:

    env_file:
      - path: ./app.env
        required: false

environment establece variables en línea

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

Se aceptan dos sintaxis: la forma de asignación anterior y una forma de lista que usa - GREETING=from_environment. Su comportamiento es idéntico. La forma de lista tiene un mecanismo adicional: una clave sin valor pasa la variable desde el shell en el que ejecutó docker compose.

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

Eso muestra from_my_shell. Si lo ejecuta sin establecer GREETING en el shell, Compose no establece nada y no muestra ninguna advertencia. Conviene conocer los fallos silenciosos del paso de variables, porque un servicio que se inicia con una variable de contraseña vacía suele iniciarse 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, después environment sin valor explícito en el archivo de 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. Compruébalo en un solo archivo.

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
    environment:
      GREETING: from_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

El primero imprime from_environment, por lo que environment: sustituyó el valor de app.env. El segundo imprime from_cli. Nada en el archivo de Compose sustituye el valor de la línea de comandos.

Cuando un contenedor se comporta como si la configuración nunca se hubiera aplicado, no hagas suposiciones. 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 mi 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 el disco, visible para cualquier usuario del grupo docker.

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 la terminal, por lo que 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 fallos de las aplicaciones suelen volcar todo el entorno en un registro o informe de errores.

La pertenencia al grupo docker equivale en la práctica a tener root en el host, por lo que no puede considerarse 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.txt

El secreto se monta en /run/secrets/db_password dentro del contenedor. El nombre después de la barra es el nombre del secreto del bloque de nivel superior secrets:.

El sufijo _FILE es una convención utilizada por Docker Official Images, incluidos postgres, mysql y mariadb. Esos 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 solo 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 normalmente pueden leer el archivo durante el inicio, o puede pasarles la ruta y hacer que su propio punto de entrada se encargue de ello.

Verifíquelo desde dentro del contenedor en ejecución:

docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD

El primer comando 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 solo muestra la ruta no confidencial.

Proteja el archivo de origen en el host, porque el secreto solo es privado en la medida en que lo sea el archivo que lo contiene:

chmod 600 db_password.txt

El punto medio práctico en un VPS

Muchas imágenes autohospedadas no admiten variables _FILE, por lo que las variables de entorno son la única opción. En un VPS administrado por una sola persona, el objetivo realista es evitar que los valores permanezcan 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.env

install -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, aunque cualquiera que pueda ejecutar docker todavía puede extraer el valor del contenedor. Añade *.env y .env a .gitignore y confirma un app.env.example que contenga los nombres de las claves con valores vacíos. Una contraseña confirmada en el repositorio debe rotarse.

Rotar un valor significa 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 que ejecutes 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.

Separar la configuración por entorno

Compose lee .env del directorio del proyecto de forma predeterminada. Indique otra ubicación con --env-file.

docker compose --env-file .env.staging config

Los archivos se leen en orden y los archivos posteriores sobrescriben a los anteriores. Mantenga los valores predeterminados que no sean secretos en un archivo incluido en el control de versiones, y los secretos en un archivo que nunca salga del servidor. Lo mismo se aplica a env_file:: si 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 solo sustituye los marcadores ${NAME} en el 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 usa sin mostrar ningún aviso.

¿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 un archivo se monta en el contenedor como un archivo sin cifrar 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 que imprimen el entorno.

¿Puedo usar comillas y espacios en un archivo env?

Use KEY=value with spaces y no ponga las comillas. Compose trata todo el resto de la línea como el valor, por lo que las comillas suelen acabar como caracteres literales dentro del valor. Nunca ponga espacios alrededor de =, porque la clave tendrá un espacio final y nada coincidirá con ella.