SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-10-11

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 similares. El archivo .env sustituye los marcadores ${VARIABLE} dentro del propio archivo compose.yaml, después de que Compose lea el YAML y antes de que valide o ejecute nada. 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. Cuando dos de ellos establecen la misma clave, el resultado depende de 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 usuario que pueda ejecutar docker inspect puede leer las variables de entorno, por lo que las contraseñas no deben almacenarse en ellas. Si no conoce bien los archivos de Compose, empiece por Conceptos básicos de Docker Compose en un VPS y vuelva aquí para configurar el entorno.

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 analizó realmente.

docker compose config

La salida muestra image: alpine:3.20. El marcador de posición ha desaparecido porque la interpolación se realizó cuando Compose cargó el archivo. Compose busca .env en el directorio del proyecto: el directorio indicado con --project-directory si lo establece; de lo contrario, el directorio del primer archivo pasado con -f; y, en última instancia, el directorio actual. Sustituye los marcadores de posición ${NAME} que encuentra en los valores YAML. Las claves nunca se interpolan y un $$ duplicado produce un signo de dólar literal en lugar de una sustitución.

A continuación, inicie 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 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, lo que es la opción correcta para un valor que no tiene un valor predeterminado seguro.

env_file carga variables en el contenedor

El atributo env_file: indica uno o varios 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 muestra from_env_file. El formato del archivo consiste en líneas simples de KEY=value, una por línea, donde # al inicio indica un comentario. No es un script de shell, aunque Compose analiza el contenido con algunas reglas similares a las del shell. Las comillas que rodean el valor se eliminan, por lo que KEY="value" y KEY=value producen el mismo resultado. En los valores entre comillas dobles y sin comillas se interpolan los marcadores ${...}, mientras que los valores entre comillas simples se usan literalmente. Se acepta el prefijo export, pero no es necesario. Los espacios alrededor del signo = se eliminan. El punto que debe vigilarse es el comentario en línea: en un valor sin comillas, un espacio seguido de # inicia un comentario. Por tanto, ponga entre comillas cualquier valor que contenga #.

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

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

environment configura 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. Se comportan de forma idéntica. Ambas tienen un comportamiento adicional: una clave sin valor transmite la variable desde el shell en el que ejecutó docker compose. En la forma de lista, se escribe como un - GREETING sin valor. En la forma de asignación, se escribe como GREETING: sin nada después de los dos puntos.

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

Esto muestra from_my_shell. Si lo ejecuta sin definir GREETING en el shell, Compose no establece ningún valor ni muestra una advertencia. Conviene conocer estos fallos silenciosos de transmisión, 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_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: 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 imprime en el terminal. Así es como termina pegado en un foro de soporte. Los procesos secundarios heredan la variable de forma predeterminada, salvo que el proceso padre los inicie deliberadamente con un entorno de reemplazo. Además, un proceso que se ejecute con el mismo usuario que PID 1 o que tenga CAP_SYS_PTRACE puede leerla desde /proc/1/environ. En un contenedor donde todo se ejecuta como root, eso incluye a todos los procesos. Una aplicación que se ejecuta como un usuario sin privilegios mientras PID 1 se ejecuta como root obtiene un error de permisos al acceder a ese archivo. Aun así, conserva la variable en su propio entorno. Además, los gestores 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 considerarse un límite de privilegios fiable. 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 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_PASSWORD

El 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.txt

El 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.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. 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 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 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 sustituye los marcadores de posición ${NAME} en el archivo compose y también puede definir variables propias de Compose, como COMPOSE_PROJECT_NAME o COMPOSE_FILE. Nunca define variables dentro de un contenedor. Para pasar el valor al 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?

Tiene prioridad environment:. El orden documentado de Docker sitúa el atributo environment por encima del atributo env_file, y ambos quedan por debajo de docker compose run -e en la línea de comandos. Si una clave se define en ambos lugares, el valor de env_file no se utiliza y no se muestra 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. En un contenedor que ya está en ejecución, docker inspect <container> --format '{{json .Config.Env}}' muestra exactamente lo que recibió su proceso.

¿Están cifrados los secretos de Compose?

No. Un secreto basado en un archivo se monta en el contenedor como un archivo de texto plano 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?

Sí. Compose elimina las comillas externas, por lo que KEY="value with spaces" y KEY=value with spaces producen value with spaces. Use comillas simples cuando el valor contenga un $ que no deba interpolarse, y ponga entre comillas cualquier valor que contenga #, porque sin comillas inicia un comentario. Los espacios alrededor de = se eliminan, por lo que KEY = value sigue definiendo KEY.

¿Te resultó útil este artículo?