Docker-compose env_file behavior is confusing

Hi everyone!

I’m facing an issue with docker-compose and env_file: directive that is driving me crazy.

I’ve this simple docker-compose.yml file:

version: "3.3"
services:
  prometheus:
    image: "prom/prometheus:latest"
    container_name: "prometheus"
    env_file:
      - prometheus/conf.d/prometheus.env
    command: ["--config.file=/etc/prometheus/prometheus.yml","--storage.tsdb.path=/var/lib/prometheus","--web.console.libraries=/usr/share/prometheus/console_libraries","--web.console.templates=/usr/share/prometheus/consoles","--web.listen-address=${LISTEN_ADDRESS}:${LISTEN_PORT}"]
    volumes:
      - type: bind
        source: prometheus/conf.d/prometheus.yml
        target: /etc/prometheus/prometheus.yml
        read_only: true

      - type: volume
        source: prometheus
        target: /var/lib/prometheus
    network_mode: "host"

My issue here is that if I do:
docker-compose --env-file prometheus/conf.d/prometheus.env -d up prometheus
it works, I get prometheus running and listen on the correct address and port.

however, if I simply do a:
docker-compose up -d prometheus
it just don’t work and log a warning such as:

WARNING: The LISTEN_ADDRESS variable is not set. Defaulting to a blank string.
WARNING: The LISTEN_PORT variable is not set. Defaulting to a blank string.

my issue here is that I don’t get the env_file directive usage, it’s confusing so far because if I do a simple:

docker-compose config
the output is confusing because:

  prometheus:
    command:
    - --config.file=/etc/prometheus/prometheus.yml
    - --storage.tsdb.path=/prometheus
    - --web.console.libraries=/usr/share/prometheus/console_libraries
    - --web.console.templates=/usr/share/prometheus/consoles
    - '--web.listen-address=:'
    container_name: prometheus
    environment:
      LISTEN_ADDRESS: 192.168.1.1
      LISTEN_PORT: '9090'
    image: prom/prometheus:latest
    network_mode: host
    volumes:
    - read_only: true
      source: /home/helyion/services/prometheus/conf.d/prometheus.yml
      target: /etc/prometheus/prometheus.yml
      type: bind
    - source: prometheus
      target: /prometheus
      type: volume

so environment variables are correctly filled, but the command directive doesn’t use it.

when doing a:
docker-compose --env-file prometheus/conf.d/prometheus.env config

I’ve got this result:

  prometheus:
    command:
    - --config.file=/etc/prometheus/prometheus.yml
    - --storage.tsdb.path=/prometheus
    - --web.console.libraries=/usr/share/prometheus/console_libraries
    - --web.console.templates=/usr/share/prometheus/consoles
    - --web.listen-address=192.168.1.1:9090
    container_name: prometheus
    environment:
      LISTEN_ADDRESS: 192.168.1.1
      LISTEN_PORT: '9090'
    image: prom/prometheus:latest
    network_mode: host
    volumes:
    - read_only: true
      source: /home/helyion/services/prometheus/conf.d/prometheus.yml
      target: /etc/prometheus/prometheus.yml
      type: bind
    - source: prometheus
      target: /prometheus
      type: volume

so here the command directive is correctly filled and environments are too, I really don’t get it.

for information here is my docker and docker-compose version log:

Docker version 20.10.8, build 3967b7d
docker-compose version 1.29.2, build unknown (pip install)

If anyone can give me an explanation, I’m all in :smiley:
thanks!

1 Like

Good morning,

I guess that your prometheus/conf.d/prometheus.env-file sets the ENV-variables LISTEN_ADDRESS and LISTEN_PORT on your Docker-host. These variables are then used within your docker-compose.yml within the command:-line.
So without the env-file the two ENV-variables on your host are empty and Docker complains about and the two variables within the docker-compose.yml are empty.
It is not the Promethous-service within the container complaining about the ENV-variables not set - it is Docker itself saying that it should use the host’s ENV-variables which are not set.
And with the wrong parameter for the --web.list-address you Promethous will not start - or at least not start as expected.

Yes, but if I understand the question correctly, the Compose file defines the following:

    env_file:
      - prometheus/conf.d/prometheus.env

Also, docker-compose config shows it knows where to find it, as part of its output is surely using the values from that file: it shows env_file: being replaced with environment:, but does not show replacements in command:, unless also specified in docker-compose --env-file ... config.

Well spotted!

the $ signs in the command must be escapped with another $ sign to delay the point where the variable expansion takes place into the containar:
This should work: "--web.listen-address=$${LISTEN_ADDRESS}:$${LISTEN_PORT}"

1 Like

@avbentem You are right - Sorry I didn’t spot the env_file: in the docker-compose.yml

When was the parameter --env-file and the env_file: introduced to docker-compose?
With

Docker version 20.10.7
docker-compose version 1.29.2, build 5becea4c

both options are available whereas with

Docker version 19.03.8
docker-compose version 1.24.1, build 4667896b

the --env-file and the env_file: are missing. But .env and ENV-variables on the Docker-host are working there, too.

1 Like

I prefer using .env, especially when Dockerfiles and all are in Git, and one can easily create a local configuration in a local .env (and docker-compose.override.yml), without fearing accidentally committing those (if added to .gitignore).

Thank you so much everyone participating, your thoughts and ideas help a lot.

However I’ve tested escaping the vars as suggested and it doesn’t work neither.
I’m really confused by the way docker-compose take the file, create the env vars but yet you can’t use them.

Am I supposed to use them with a special notation?

TBN: Using environment without env_file and call for ENVs doesn’t work.
TBN2: Using a .env file put on the same directory as the docker-compose.yml IS actually working.

Odd, escapping should have worked.

You can find the documatation about escaping the $ character at the end of the variable-substitution chapter in the compose file reference

I used it in the past, though don’t realy remember if it was in combination with a .env file or an env_file: declaration or was just used to reference a fixed environment declaration in a command: entry.

Still it’s odd that .env and env_files seem to operate with different scopes. The first seem to be available in the general scope of the compose file, while the second seem to have limited scope on the environments level.

I filled a bug with prometheus team as I tested the behavior with a hand made container using Ubuntu and dumb-init to just launch a command as:

command: [
'bash',
'-c',
'echo "$${LISTEN_ADDRESS}:$${PORT}"'
]

using only env_file directive and it works!

So I guess it may be a hiccup with the way Alpine based images are created as it seems alpine is quite different from other Linux based distribution.

Does docker-compose config also show the substituted values for that minimal example? (Just curious if config always works as expected for debugging.)

So, docker-compose config output as is:

services:
  debug:
    build:
      context: /home/flint/services/debug
    command:
    - bash
    - -c
    - echo "$${LISTEN_ADDRESS}:$${LISTEN_PORT}"
    container_name: debug
    environment:
      LISTEN_ADDRESS: 192.168.1.74
      LISTEN_PORT: '9090'
    image: debug
    network_mode: host
    volumes:
    - read_only: true
      source: /home/flint/services/debug/conf.d/debug.conf
      target: /etc/debug/debug.conf
      type: bind

and the docker inspect is correct too:

        "Config": {
            "Hostname": "cmc01.flint.net",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "LISTEN_ADDRESS=192.168.1.74",
                "LISTEN_PORT=9090",
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "DHCPD_PROTOCOL=4",
                "DHCPD_INTERFACE=eth0",
                "DHCPD_DEFAULT_CONFIG=/etc/dhcpd/dhcpd.conf"
            ],

What’s really weird is when using .env or --env-file command line with the Prometheus container it works, only env_file variable injection seems to be not correctly interpreted.

I’m sorry if I am adding confusion, but the following is for the working “hand made container using Ubuntu and dumb-init”, right?

Above, docker-compose config is not showing all substitutions, but instead shows echo "$${LISTEN_ADDRESS}:$${LISTEN_PORT}". (As expected; see note below.) But the output of the running container is as expected? If that’s what you’re saying: did you actually test the running container in your earlier attempt to use $ and $$, or did you only use docker-compose config to see the substitutions?

I guess you could easily repeat the minimal working example with Alpine?

And oh, I only realized after writing the above but I guess you know if you’d already read the documentation that @meyay pointed at, but not seeing $$ being substituted is just as expected:

You can use a $$ (double-dollar sign) when your configuration needs a literal dollar sign. This also prevents Compose from interpolating a value, so a $$ allows you to refer to environment variables that you don’t want processed by Compose.

(So I guess in your case you’d actually not want $$ double-dollar signs.)

No no, I’m not expecting to get $$VAR substituted within config, it’s just there to show you that Environment variables are actually correctly crafted as a command that will be interpretated later on by the container environment.

Yes this exemple using bash is for the working ubuntu based container and yes everything works according to the spec:

The docker-compose build the command with the appropriate escape character that it then used by bash to substitute it with the inner environment vars filled within the ENV section.

The output is then correct with an echo showing: 192.168.1.74:9090

Prometheus team answered me, they’re endpointing the container directly to /bin/prometheus without any appropriate terminal context, meaning that it’s not expected to substitute anything correctly.

Weird thing is, how is it working using the --env-file flag where this flag is doing the exact same thing than env_file config directive?

EDIT:
OK, I thing I find out what’s going on in here.

We’re in a very specific situation where the prometheus container is build without any terminal, it means that it can’t substitute escaped variables as bash would do.

So, this doesn’t work when:
You create a docker-compose with env_file providing variables to the inner container that are also used to build the command directive.
You create a docker-compose with env_file providing variables to the inner container that are used as is without escape by the command directive to build the final container CMD instruction as docker-compose CLI terminal context don’t have the variables available.

So it work when:
You create a docker-compose and use --env-file to provide the variables as the docker-compose CLI is then creating the variables temporarily within it’s terminal context and then use them to build the not escaped command, that once it’s use by docker as the final CMD instruction is provided to the container ALREADY substituted.

So all in all, it work as intended from the docker part of the issue, what’s completely broken in here is prometheus container that doesn’t provide any terminal context and so can’t really use variables substitution even if the ENV container variable is perfectly filled.

What’s really really weird with their container is that it almost work as the prometheus container succesfully translate the first part of the command…

Thread is somewhat old, but with “env_file not working” request brought me here. And I was strugging with similar problem. However, part was broken for me is export word for variables - that’s actually how .env file should be written:

export PORT=3000
export HOST=127.0.0.1

While many tools (especially libraries) do ignore export part & just parse KEY=VALUE pairs, but technically env file is just a bash script - so export directive should be used to export value outside a bash script. After fixing it everything start working smoothly.

1 Like

If using export solves this, then my guess would be that your shell is handling the file, not Docker? :thinking: Where is your .env file located?

If your shell is handling it, it will indeed change the environment that any subsequent Docker command sees in that session too. But that changed environment will also apply to any non-Docker commands in that session. Whether or not that is what you want is up to you, of course.

1 Like

I agree with @avbentem but I didn’t know I could use export before the variables. It is actually solves one of my old problems, since I wanted to parse Docker’s .env file from shell, but this way I don’t even need to.

There is something else however, that you may not know. Docker Compose v2 supports using the .env file as yaml, so you can use “:” instead of “=”. It can be useful if you prefer YAML instead of bash-like syntax. I don’t know if it is in any documentation. I discovered it by accident when I realized it was a yaml after I ran Docker Compose without error. If you use the yaml syntax, you can’t start it with export.

I consider current behavior as a bug.

When I set environment in .env file, I can use that variable in ports for example

ports:
  - ${MYSQL_CUSTOM_PORT}:3306

When I set same environment ONLY in some .env.local file and include it via env_file: .env.local in docker-compose.yml or docker-compose.override.yml, that variable IS NOT SET before evaluating docker-compose.yml file itself, therefore warning comes “The MYSQL_CUSTOM_PORT variable is not set. Defaulting to a blank string.” and docker-compose up itself crashes with “services.mysql.ports contains an invalid type, it should be a number, or an object”

It behaves differently for implicitly evaluated .env and explicitly evaluated .some-env-file

Confusing, yes, but it works as expected. .env file is to define variables that you can use instead of setting environment variables on the host. These variables can be used as placeholders in the yaml file. Files that you can refer to using env_file is for setting environment variables inside the containers directly.

1 Like