Docker Community Forums

Share and learn in the Docker community.

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…