compose icon indicating copy to clipboard operation
compose copied to clipboard

[BUG] Environment variables not loaded from the 2nd env_file in the command shell

Open marcindulak opened this issue 1 year ago • 4 comments

Description

It appears that environment variables are not loaded from the 2nd env_file, during the execution of a shell in command. Only the variables from the 1st env_file are loaded. On the other hand, after the container is up, execing into the container shows also the variables loaded from the 2nd env_file.

Steps To Reproduce

  1. Run docker compose up, with the following docker-compose.yml

    services:
      test:
        image: nginx:stable-bullseye
        env_file:
          - .env
          - .env.1
        command: bash -c 'printenv TEST && echo $TEST'
    

    the following .env

    TEST=0
    

    and .env.1 file

    TEST=1
    

    This produces the following, unexpected output. I believe the 2nd output line should also be 1, since the .env.1 is expected to "win" https://github.com/docker/docs/pull/3822

    test-1  | 1
    test-1  | 0
    

    Note that printenv shows the expected value, but echo does not.

  2. Despite this behavior, the config suggest the environment should contain TEST=1 as expected

    docker compose --env-file .env --env-file .env.1 config
    name: test
    services:
      test:
        command:
          - bash
          - -c
          - printenv TEST && echo 1
        environment:
          TEST: "1"
        image: nginx:stable-bullseye
        networks:
          default: null
    networks:
      default:
        name: test_default
    
  3. Run the container in detached mode, then exec and echo the variable

     cat docker-compose.yml | sed 's/command: .*/command: bash -c "sleep infinity"/' | docker compose -f - up -d
     docker compose exec test bash -c 'echo $TEST'
    

    Output is 1 as expected.

  4. Note that the problem seems to be specific to docker compose, docker itself behaves as expected

    docker run --rm --name test --env-file .env --env-file .env.1 nginx:stable-bullseye bash -c 'printenv TEST && echo $TEST'
    

    Output

    1
    1
    

Compose Version

Docker Compose version v2.21.0
Docker Compose version v2.21.0

Docker Environment

Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Docker Buildx (Docker Inc., v0.9.1-docker)
  compose: Docker Compose (Docker Inc., v2.21.0)
  scan: Docker Scan (Docker Inc., v0.23.0)

Server:
 Containers: 18
  Running: 0
  Paused: 0
  Stopped: 18
 Images: 219
 Server Version: 24.0.7
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 3dd1e886e55dd695541fdcd67420c2888645a495
 runc version: v1.1.10-0-g18a0cb0
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
 Kernel Version: 5.15.0-91-generic
 Operating System: Ubuntu 20.04.6 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 31.2GiB
 Name: test
 ID: XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Username: test
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

Anything else?

Tested also with

Docker Compose version v2.23.3

and

Client:
 Version:    24.0.7-rd
 Context:    rancher-desktop
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.12.0
    Path:     /Users/test/.docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.23.3
    Path:     /Users/test/.docker/cli-plugins/docker-compose

Server:
 Containers: 4
  Running: 0
  Paused: 0
  Stopped: 4
 Images: 37
 Server Version: 23.0.6
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 0cae528dd6cb557f7201036e9f43420650207b58
 runc version: 860f061b76bb4fc671f0f9e900f7d80ff93d4eb7
 init version: 
 Security Options:
  seccomp
   Profile: builtin
 Kernel Version: 6.1.64-0-virt
 Operating System: Alpine Linux v3.18
 OSType: linux
 Architecture: aarch64
 CPUs: 4
 Total Memory: 7.749GiB
 Name: lima-rancher-desktop
 ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

marcindulak avatar Jan 18 '24 18:01 marcindulak

Tested with latest codebase, and I get the expected result:

Attaching to test-1
test-1  | 1
test-1  | 1
test-1 exited with code 0

Can you please upgrade to latest release ?

ndeloof avatar Jan 22 '24 13:01 ndeloof

The latest vesion I have access to today is

docker compose version
Docker Compose version v2.24.1

and it still has the issue.

marcindulak avatar Jan 22 '24 20:01 marcindulak

@marcindulak https://github.com/docker/compose/releases/tag/v2.24.2

ndeloof avatar Jan 22 '24 21:01 ndeloof

Still no improvement (it's on Ubuntu 20.04)

docker compose version
Docker Compose version v2.24.2

docker compose up
[+] Running 2/2
 ✔ Network tmp_default   Created                                                                                                                                                                    0.1s 
 ✔ Container tmp-test-1  Created                                                                                                                                                                    0.1s 
Attaching to test-1
test-1  | 1
test-1  | 0
test-1 exited with code 0
docker info
Client: Docker Engine - Community
 Version:    25.0.0
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.12.1
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.24.2
    Path:     /home/test/.docker/cli-plugins/docker-compose

marcindulak avatar Jan 23 '24 17:01 marcindulak

Guess I understood the confusion here: As you set command in you compose file with command: bash -c 'printenv TEST && echo $TEST' the $TEST value is interpolated by compose during parsing, using your .env file to get variables. .env.1 is not involved here. You can confirm this by running docker compose config If you want to get $TEST to be passed to your command as a shell variable, you have to double the dollar sign so compose won't parse it: command: bash -c 'printenv TEST && echo $$TEST'. And then you get the expected result:

$docker compose run test
1
1

ndeloof avatar Jan 25 '24 09:01 ndeloof

I would say this quite an unexpected behavior. Can you point to an official documentation that requires double dollars?

Otherwise, please keep the issue open until it's documented.

marcindulak avatar Jan 25 '24 16:01 marcindulak

https://github.com/compose-spec/compose-spec/blob/master/12-interpolation.md

ndeloof avatar Jan 25 '24 16:01 ndeloof

There is still one thing unclear: what is meant by parsing - or why only .env is involved when parsing? I don't have TEST=0 set in the shell that invokes docker compose up, neither use anything that would load the variables from .env in that shell.

Do you mean that .env has some kind of special meaning for docker compose, and it's used for that parsing even if .env is not listed in docker-compose.yml? It appears that if I remove .env from disk, and update docker-compose.yml to use .env.1 and .env.2, then the TEST variable is undefined during that parsing.

marcindulak avatar Jan 25 '24 17:01 marcindulak

I think this behavior requires a clarification in the docker compose docs. See the bottom of this comment for an attempt of a summary.

The special behavior of .env is further clarified at https://stackoverflow.com/questions/46455610/usage-of-env-variable-in-docker-compose-run-command/46456139#46456139

On the other hand, https://docs.docker.com/compose/environment-variables/set-environment-variables/#additional-information says

You can use multiple .env files in your compose.yml with the env_file attribute, and Docker Compose reads them in the order specified. If the same variable is defined in multiple files, the last definition takes precedence

Screenshot from 2024-01-27 08-45-22

Here is the example from that page modified to make it runnable

services:
  webapp:
    image: nginx:stable-bullseye
    env_file:
      - .env
      - .env.override
    command: bash -c 'echo $TEST'

.env

TEST=0

.env.override

TEST=1

This is the current behavior of the example

docker compose version
Docker Compose version v2.24.2

docker compose up
[+] Running 1/0
 ✔ Container tmp-webapp-1  Created                                                                                                                                                                  0.0s 
Attaching to webapp-1
webapp-1  | 0
webapp-1 exited with code 0

I find the setence If the same variable is defined in multiple files, the last definition takes precedence does not describe the current behavior sufficiently. The main problem is that there is a difference in behavior between command: bash -c 'echo $TEST' in docker-compose.yml, and using this command for container run or exec, as shown below

No current shell variable interpolation, due to single quotes

docker compose run webapp sh -c 'echo $TEST'
1

With current shell variable interpolation, due to double quotes

docker compose run webapp sh -c "echo $TEST"

I would interpret that takes precedence in this case suggests to the user that TEST=1 is used, in the same way as both docker run and exec do it.

Please add a warning somewhere around https://docs.docker.com/compose/environment-variables/set-environment-variables/#additional-information, which clarifies some of these observations.

To summarize: the issue is that the behavior of command placed in docker-compose.yml file differs from the behavior of the command of docker compose run and exec.

marcindulak avatar Jan 27 '24 10:01 marcindulak

Hi @ndeloof and @Danio84, Friendly question from a Docker Captain. Why is this issue closed? I can still reproduce the faulty behaviour. Best :)

ldynia avatar Feb 26 '24 08:02 ldynia

@ldynia have you read https://github.com/docker/compose/issues/11373#issuecomment-1909695119 ?

the root cause for this is

  1. misunderstanding about env_file which isn't involved in variable interpolation but .env is loaded by default for this purpose
  2. $TEST set in command being interpolated during parsing, not expanded by container's shell based on environment

ndeloof avatar Feb 26 '24 09:02 ndeloof

Thanks @ndeloof, No, I didn't read it. Now it makes sense $$

ldynia avatar Feb 26 '24 09:02 ldynia