compose icon indicating copy to clipboard operation
compose copied to clipboard

Compose V1 doesn't behave as V2 when referencing environment variables across multiple .env files

Open Nico769 opened this issue 2 years ago • 9 comments

Description

Environment variable substitution doesn't seem to behave consistently in Compose V1 and V2 when:

  • multiple .env files are used AND;
  • one .env file declares a variable that is referencing a different variable, declared in another .env file.

Steps to reproduce the issue:

See the following minimal reproducible example with some (hopefully!) clarifying comments

.env file

TO_REFERENCE="world!"

# Using TO_REFERENCE variable here works,
# either when `docker-compose up` or `docker compose up` is being used.
# Therefore, variable substitution happens as expected.
WORKING_SUBSTITUTION="Hello ${TO_REFERENCE}"

.another.env

# Using TO_REFERENCE variable here doesn't work when `docker-compose up` is being used,
# while `docker compose up` makes the substitution happen as expected.
# 
# In other words, the final value should be "Hello beautiful world!"
# but the variable is left empty if `docker-compose up` is being used. 
NON_WORKING_SUBSTITUTION="Hello beautiful ${TO_REFERENCE}"

docker-compose.yaml

version: '3.9'

services:
  helloworld:
    container_name: hello-world
    image: hello-world
    env_file:
      - .env 
      - .another.env

Describe the results you received:

Running docker inspect hello-world | grep -C 5 Env shows how Hello beautiful is assigned to NON_WORKING_SUBSTITUTION environment variable if docker-compose up is being used

[...]
"Env": [
    "TO_REFERENCE=world!",
    "WORKING_SUBSTITUTION=Hello world!",
    "NON_WORKING_SUBSTITUTION=Hello beautiful ",
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],

Describe the results you expected:

Running docker inspect hello-world | grep -C 5 Env should give the following if docker-compose up is being used

[...]
"Env": [
    "TO_REFERENCE=world!",
    "WORKING_SUBSTITUTION=Hello world!",
    "NON_WORKING_SUBSTITUTION=Hello beautiful world!",
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],

Output of docker compose version:

Docker Compose version v2.7.0

Output of docker-compose version:

docker-compose version 1.29.2, build 5becea4c
docker-py version: 5.0.0
CPython version: 3.9.0
OpenSSL version: OpenSSL 1.1.1g  21 Apr 2020

Output of docker version:

Client: Docker Engine - Community
 Cloud integration: v1.0.28
 Version:           20.10.17
 API version:       1.41
 Go version:        go1.17.11
 Git commit:        100c701
 Built:             Mon Jun  6 23:03:17 2022
 OS/Arch:           windows/amd64
 Context:           default
 Experimental:      true

Server: Docker Desktop
 Engine:
  Version:          20.10.17
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.17.11
  Git commit:       a89b842
  Built:            Mon Jun  6 23:01:23 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.6
  GitCommit:        10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1
 runc:
  Version:          1.1.2
  GitCommit:        v1.1.2-0-ga916309
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Output of docker info:

Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.8.2)
  compose: Docker Compose (Docker Inc., v2.7.0)
  extension: Manages Docker extensions (Docker Inc., v0.2.8)
  sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc., 0.6.0)
  scan: Docker Scan (Docker Inc., v0.17.0)

Server:
 Containers: 2
  Running: 1
  Paused: 0
  Stopped: 1
 Images: 19
 Server Version: 20.10.17
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  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.runtime.v1.linux runc io.containerd.runc.v2
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1
 runc version: v1.1.2-0-ga916309
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 5.10.60.1-microsoft-standard-WSL2
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 5.8GiB
 Name: docker-desktop
 ID: 2OTH:M7O7:3J54:BFMN:YT4P:6KV5:Y66L:225T:H4UZ:FNL3:L3ES:JWP7
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 HTTP Proxy: http.docker.internal:3128
 HTTPS Proxy: http.docker.internal:3128
 No Proxy: hubproxy.docker.internal
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  hubproxy.docker.internal:5000
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No blkio throttle.read_bps_device support
WARNING: No blkio throttle.write_bps_device support
WARNING: No blkio throttle.read_iops_device support
WARNING: No blkio throttle.write_iops_device support

Cheers!

Nico769 avatar Aug 05 '22 16:08 Nico769

This was supposed to be fixed by https://github.com/docker/compose/pull/9636 in v2.7, but I'm also not seeing any changes at all. I'm forced to downgrade to Compose 1 as well (again....) as Compose 2 keeps breaking production environments with undefined, messy and inconsistent env file handling.

curry684 avatar Aug 08 '22 10:08 curry684

Thanks for sharing your thoughts and the PR @curry684!

I'd argue that V2, in this instance, is behaving correctly while V1 has a bug somewhere in the variables resolution part of the code. Indeed, my example shows that V1 doesn't build the string as expected while V2 does.

Nico769 avatar Aug 10 '22 09:08 Nico769

I am having a similar issue. I have the "env_file" config with just one ".env.test" file connected and the container inside has env variables randomly taken, some from .env.test and some from .env

eg.

#.env file
VAR_1=env1
VAR_2=env2
#.env.test file
VAR_1=env_test1
VAR_2=env_test2
env_file:
     - .env.test

printing ENV variables from the inside of the container

VAR_1=env1
VAR_2=env_test2

It worked fine in docker 4.6.1 (docker used variables only from the .env.test file) but stopped working correctly after updating to 4.10.

radoslawkoziol avatar Aug 10 '22 22:08 radoslawkoziol

@Nico769 As you stated in https://github.com/docker/compose/issues/9725#issuecomment-1210425431 the behaviour in V2 is the expected. What happens is that V2 evaluates the variables in .env (with precedence over the OS env) making them available for interpolation.

ulyssessouza avatar Aug 13 '22 14:08 ulyssessouza

@radoslawkoziol I just tested your test case with Docker Compose v2.9.0 and got the expected result:

VAR_1=env_test1
VAR_2=env_test2

ulyssessouza avatar Aug 13 '22 14:08 ulyssessouza

@radoslawkoziol I just tested your test case with Docker Compose v2.9.0 and got the expected result:

I mean, I know it sounds weird but it happens randomly. Sometimes it works fine, sometimes it doesn't, without changing anything, just after docker restart. It's not only my PC, My colleague's one behaves the same.

Is there something I can send you to help debug? Some logs?

radoslawkoziol avatar Aug 13 '22 14:08 radoslawkoziol

@radoslawkoziol Did you try it with the latest version (v2.9.0)?

Actually the evaluation is not meant to be concurrent, so it should not have any race condition problems. Could you please post a minimal test case with all the relevant files?

ulyssessouza avatar Aug 13 '22 20:08 ulyssessouza

@ulyssessouza I'm not sure how to update windows docker-compose to 2.9. My docker desktop app shows I have the newest version of docker installed.

docker-compose.yml

version: "3.7"
services:
  test:
    image: nginx:alpine
    env_file:
      - .env.adapter

.env

MYSQL_HOST=middleware_db
TEST1=env_test1
TEST2=env_test2
TEST3=env_test3

.env.adapter

MYSQL_HOST=adapter_db
TEST1=env_adapter_test1
TEST2=env_adapter_test2
TEST3=env_adapter_test3

Docker v2 (Docker Compose version v2.7.0) result: image

image

Docker v1 (docker-compose version 1.29.2, build 5becea4c) result: image image

Operating system: W10 + WSL2

Docker version: image

radoslawkoziol avatar Aug 14 '22 18:08 radoslawkoziol

Hi @ulyssessouza, thanks for confirming that!

I wanted to assess whether this is indeed a bug in V1, so I tried to run the test suite (i.e. make test on git tag 1.29.2) but I've came across several errors during the build process, some of which I managed to fix. I've also tried to join the community Slack channel to ask for help, but apparently I need an invite of some sort.

Since I'm not sure this is the right place to troubleshoot my build errors, I'm attaching the test case I've come up with in order to investigate further into the matter. Hopefully it's helpful to others while I figure out what's wrong with the build.

tests/integration/service_test.py

    def test_env_variables_interpolation_across_multiple_env_files(self):
        service = self.create_service(
            'web',
            env_file=['tests/fixtures/env/first.env', 'tests/fixtures/env/referencing.env'])
        env = create_and_start_container(service).environment
        for k, v in {
            'ONE': '1',
            'FOO': 'bar',
            'INTERPOLATION': 'barbaz'
        }.items():
            assert env[k] == v

tests/fixtures/env/first.env

# first.env

ONE=1
FOO=bar

tests/fixtures/env/referencing.env

# referencing.env

INTERPOLATION=${FOO}baz

Cheers.

Nico769 avatar Aug 27 '22 19:08 Nico769

tested with latest v2.17.3 release, and get the expected variable set:

TO_REFERENCE=world!
WORKING_SUBSTITUTION=Hello world!
NON_WORKING_SUBSTITUTION=Hello beautiful world!

so, closing this issue as fixed. Feel free to comment or open a new issue if some corner cases haven't been addressed

ndeloof avatar May 03 '23 12:05 ndeloof