通过 docker-compose 将变量从 .env 文件传递​​到 docker 文件

Pass variables from .env file to dockerfile through docker-compose

project
└───app
│   │   ...
│   │   Dockerfile
│   │
└───prod.env
└───docker-compose.yml

我的 docker-compose 看起来像这样:

services:
   app:
      build:
         context: .\app
         args:
            ARG1: val1
            ARG2: val2
      env_file:
         - prod.env

但我也试过这个:

services:
   app:
      build:
         context: .\app
         args:
            ARG1: ${ARG1}
            ARG2: ${ARG2}
      env_file:
         - prod.env

我的 prod.env 文件如下所示:

ARG1 = 'val1'
ARG2 = 'val2'

但我也试过这个:

ARG1=val1
ARG2=val2

我想将 args 的值或 prod.env 文件中的值传递给 docker 文件。

这是我试图得到的:

ARG ARG1
ARG ARG2

RUN echo ${ARG1}
RUN echo ${ARG2}
ENV ARG1 ${ARG1}
ENV ARG2 ${ARG2}

RUN echo ${ARG1}
RUN echo ${ARG2}
ENV ARG1 "new val2"
ENV ARG2 "new val2"

RUN echo ${ARG1}
RUN echo ${ARG2}

它总是以空白值结尾。

如有任何帮助,我们将不胜感激。当我尝试其他帖子时,我觉得没有答案有效。

构建我使用 docker-compose --env-file prod.env build

谢谢

更新 Sergio Santiago 问我是否可以 运行 docker-compose config 并显示结果。

这是我用于此测试的最终文件。

docker-撰写:

services:
   app:
      build:
         context: .\app
         args:
            ARG1: val1
            ARG2: val2
      env_file:
         - prod.env

prod.env:

ARG3 = 'val3'
ARG4 = 'val4'

这里是 docker-compose --env-file prod.env config

的输出
networks:
  demo-net: {}
services:
  app:
    build:
      args:
        ARG1: val1
        ARG2: val2
      context: C:\project\app
    environment:
      ENV: prod.env
      ARG3: val3
      ARG4: val4

我想清楚地补充一点,从这里获取变量从 .env 文件到 docker-compose 文件不是问题。我在容器上还有一个 Flask 应用 运行ning,通过 os.environ 它能够使用 .env 文件中的变量。我只是不知道如何为 Dockerfile 提供相同的访问权限。

更新 2 与 ErikMD 的回答相关的更多具体信息

prod.env

DOMAIN = 'actualdomain.com'
ENV = 'prod.env'
ENV_NUM = 1
ARG1 = 'value1'

dev.env

DOMAIN = 'localhost'
ENV = 'dev.env'
ENV_NUM = 0
ARG1 = 'value1'

请注意,ARG1 的值相同,但其他值不同。

docker-compose.yml

version: "3.7"
services:
  home:
    image: home-${ENV_NUM}
    build: 
      context: .\home
      args:
        ARG1: "${ARG1}"
    networks:
      - demo-net
    env_file:
      - ${ENV}
    labels:
      - traefik.enable=true
      - traefik.http.routers.home.rule=Host(`${DOMAIN}`)
      - traefik.http.routers.home.entrypoints=web
    volumes:
      - g:\:c:\sharedrive
...
...
  reverse-proxy:
    restart: always
    image: traefik:v2.6.1-windowsservercore-1809
    command:
      - --api.insecure=true
      - --providers.docker=true
      - --entrypoints.web.address=:80
      - --providers.docker.endpoint=npipe:////./pipe/docker_engine
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    networks:
     - demo-net
    volumes:
      - source: \.\pipe\docker_engine\
        target: \.\pipe\docker_engine\
        type: npipe
networks:
  demo-net:

圆点表示其他应用程序的格式与主页相同。

docker文件

FROM python:3.10.3

ARG ARG1="default"

ENV ARG1="${ARG1}"

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN echo "This is argument 1 -> ${ARG1}"

docker-compose --env-file prod.env config

的输出
networks:
  demo-net: {}
services:
  home:
    build:
      args:
        ARG1: value1
      context: C:\MIS-Web-App\home
    environment:
      DOMAIN: actualdomain.com
      ENV: prod.env
      ENV_NUM: '1'
      ARG1: value1
    image: home-1
    labels:
      traefik.enable: "true"
      traefik.http.routers.home.entrypoints: web
      traefik.http.routers.home.rule: Host(`mis.canaras.net`)
    networks:
      demo-net: null
    volumes:
    - g:\:c:\sharedrive:rw
...
...

然后我运行要么docker-compose --env-file prod.env build要么docker-compose --env-file dev.env build

构建的输出

Step 9/23 : RUN echo "This is argument 1 -> ${ARG1}"
 ---> Running in 5142850de365
This
is
argument
1
->
Removing intermediate container 5142850de365

现在我在命令和实际文件中调用传递 env_file,因为那里有我的 docker-compose 文件需要的变量和我的烧瓶应用程序需要的变量。而且肯定有重叠。

从 prod.env 或 dev.env 文件获取值到 docker-compose 不是问题。也没有将它放到我的烧瓶应用程序中。问题是将这些值获取到 docker 文件。

您可以使用 docker 中的 .env file 组合,这样您就可以使用在服务定义中定义的相同时间:

services:
    app:
         build:
            context: .\app
            args:
               ARG1: ${ARG3}
               ARG2: ${ARG4}
         env_file:
            - .env

这样两者可以共享同一个env文件,但是你仍然有将变量重新定义为占位符的缺点。

这是一个建议,选择一个更适合你的

我发布了一个新答案以强调与 OP 问题相关的各种假设,特别是 ".env" 唯一文件名和 *.env 文件之间存在细微差别这一事实( env_file:).

的参数

但是除了这个微妙之处,将参数从 docker-compose.yml 传递到 docker build -f Dockerfile . and/or docker run -e … 的过程很简单,如下面的综合示例所示。

最小工作示例

让我们考虑给定目录中的以下文件,比如 ./docker

文件docker-compose.yml:

services:
  demo-1:
    image: demo-${ENV_NUM}
    build:
      context: .
      args:
        ARG1: "demo-1/${ARG1}"
        ARG3: "demo-1/${ARG3}"
  demo-2:
    image: demo-2${ENV_FILE_NUM}
    build:
      context: .
      args:
        ARG1: "demo-2/${ARG1}"
        ARG3: "demo-2/${ARG3}"
    env_file:
      - var.env

备注: 即使我们使用 build: 字段,最好也添加一个 image: 字段来自动标记构建图像;但请注意,这些图像名称必须成对不同。

文件.env:

KEY="some value"
ENV_NUM=1
ARG1=.env/ARG1
ARG2=.env/ARG2
ARG3=.env/ARG3

文件var.env:

ENV_FILE_NUM="some number"
ARG1=var.env/ARG1
ARG2=var.env/ARG2
ARG3=var.env/ARG3
ARG4=var.env/ARG4

文件Dockerfile:

FROM debian:10

# Read build arguments (default value if omitted at CLI)
ARG ARG1="default 1"
ARG ARG2="default 2"
ARG ARG3="default 3"

# the build args are exported at build time
RUN echo "ARG1=${ARG1}" | tee /root/arg1.txt
RUN echo "ARG2=${ARG2}" | tee /root/arg2.txt
RUN echo "ARG3=${ARG3}" | tee /root/arg3.txt

# Export part of these args at runtime also
ENV ARG1="${ARG1}"
ENV ARG2="${ARG2}"

# exec-form is mandatory for ENTRYPOINT/CMD
CMD ["/bin/bash", "-c", "echo ARG1=\"${ARG1}\" ARG2=\"${ARG2}\" ARG3=\"${ARG3}\"; echo while at build time:; cat /root/arg{1,2,3}.txt"]

实验课 1

首先,, a very handy command to preview the effective docker-compose.yml file after interpolation is docker-compose config

$ docker-compose config

WARN[0000] The "ENV_FILE_NUM" variable is not set. Defaulting to a blank string. 
name: docker
services:
  demo-1:
    build:
      context: /home/debian/docker
      dockerfile: Dockerfile
      args:
        ARG1: demo-1/.env/ARG1
        ARG3: demo-1/.env/ARG3
    image: demo-1
    networks:
      default: null
  demo-2:
    build:
      context: /home/debian/docker
      dockerfile: Dockerfile
      args:
        ARG1: demo-2/.env/ARG1
        ARG3: demo-2/.env/ARG3
    environment:
      ARG1: var.env/ARG1
      ARG2: var.env/ARG2
      ARG3: var.env/ARG3
      ARG4: var.env/ARG4
      ENV_FILE_NUM: some number
    image: demo-2
    networks:
      default: null
networks:
  default:
    name: docker_default

此处,如警告所示,我们发现插值 ENV_FILE_NUM 存在问题,尽管 var.env 提到了该变量。原因是 env_files 行只是为基础 docker run -e … 命令添加新的环境变量,但 不插入任何东西docker-compose.yml.

相反,可以注意到取自 ".env" 的值 ARG1=.env/ARG1 插值到 [=29= 的 args: 字段中],比照。输出行:

args:
  ARG1: demo-1/.env/ARG1
  …

".env"env_file 的这种截然不同的语义在 this page of the official documentation 中有描述。

实验课 2

接下来,让我们运行:

$ docker-compose up --build
                                                                                         
WARN[0000] The "ENV_FILE_NUM" variable is not set. Defaulting to a blank string.                                                                     
[+] Building 10.4s (13/13) FINISHED
 => [demo-1 internal] load build definition from Dockerfile
 => => transferring dockerfile: 609B
 => [demo-2 internal] load build definition from Dockerfile
 => => transferring dockerfile: 609B
 => [demo-1 internal] load .dockerignore
 => => transferring context: 2B
 => [demo-2 internal] load .dockerignore
 => => transferring context: 2B
 => [demo-2 internal] load metadata for docker.io/library/debian:10
 => [demo-2 1/4] FROM docker.io/library/debian:10@sha256:ebe4b9831fb22dfa778de4ffcb8ea0ad69b5d782d4e86cab14cc1fded5d8e761
 => => resolve docker.io/library/debian:10@sha256:ebe4b9831fb22dfa778de4ffcb8ea0ad69b5d782d4e86cab14cc1fded5d8e761
 => => sha256:85bed84afb9a834cf090b55d2e584abd55b4792d93b750db896f486680638344 50.44MB / 50.44MB
 => => sha256:ebe4b9831fb22dfa778de4ffcb8ea0ad69b5d782d4e86cab14cc1fded5d8e761 1.85kB / 1.85kB
 => => sha256:40dd1c1b1c36eac161ab63b6ce3a57d56ad79a667a37717a31721bac3f30aaf9 529B / 529B
 => => sha256:26a2b081e03207d26a105340161109ba0f00e857cbb0ff85aaeeeadd46b709c5 1.46kB / 1.46kB
 => => extracting sha256:85bed84afb9a834cf090b55d2e584abd55b4792d93b750db896f486680638344
 => [demo-2 2/4] RUN echo "ARG1=demo-2/.env/ARG1" | tee /root/arg1.txt
 => [demo-1 2/4] RUN echo "ARG1=demo-1/.env/ARG1" | tee /root/arg1.txt
 => [demo-1 3/4] RUN echo "ARG2=default 2" | tee /root/arg2.txt
 => [demo-2 3/4] RUN echo "ARG2=default 2" | tee /root/arg2.txt
 => [demo-2 4/4] RUN echo "ARG3=demo-2/.env/ARG3" | tee /root/arg3.txt
 => [demo-1 4/4] RUN echo "ARG3=demo-1/.env/ARG3" | tee /root/arg3.txt
 => [demo-2] exporting to image
 => => exporting layers
 => => writing image sha256:553f294a410ceeb3c0ac9d252d443710c804d3f7437ad7fffa586967517f5e7a
 => => naming to docker.io/library/demo-1
 => => writing image sha256:84bb2bd0ffae67ffed0e74efbf9253b6d634a6f37c6f99bc4eedea81846a9352
 => => naming to docker.io/library/demo-2
                                     
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 3/3              
 ⠿ Network docker_default     Created
 ⠿ Container docker-demo-1-1  Created
 ⠿ Container docker-demo-2-1  Created
Attaching to docker-demo-1-1, docker-demo-2-1

docker-demo-1-1  | ARG1=demo-1/.env/ARG1 ARG2=default 2 ARG3=
docker-demo-1-1  | while at build time:
docker-demo-1-1  | ARG1=demo-1/.env/ARG1
docker-demo-1-1  | ARG2=default 2
docker-demo-1-1  | ARG3=demo-1/.env/ARG3

docker-demo-2-1  | ARG1=var.env/ARG1 ARG2=var.env/ARG2 ARG3=var.env/ARG3
docker-demo-2-1  | while at build time:
docker-demo-2-1  | ARG1=demo-2/.env/ARG1
docker-demo-2-1  | ARG2=default 2
docker-demo-2-1  | ARG3=demo-2/.env/ARG3

docker-demo-1-1 exited with code 0
docker-demo-2-1 exited with code 0

在这里,我们可以再次看到".env"值和file_env: [ filename.env ]值扮演不同的角色,不重叠。

此外:

  • 鉴于缺少 Dockerfile 命令行 ENV ARG3="${ARG3}",build-arg ARG3 的值不会在 运行 时间传播(参见 ARG3=上面输出中的行)。
  • 但是,如果 docker-compose.yml 文件中 environment:env_file: 部分的值是 defined/overidden,无论如何都可以在 运行 时导出该值(请参阅上面输出中的 ARG3=var.env/ARG3 行。

有关详细信息,请参阅 ARG 指令的文档。

关于 docker-compose --env-file 选项的备注 use-case

正如 OP 所提到的,docker-compose 也有一个有用的 CLI 选项 --enf-file(不幸的是,它与非常不同的 env-file: 字段的命名方式完全相同,但没关系)。

此选项允许以下 use-case(摘自 OP 的代码):

文件docker-compose.yml:

services:
  home:
    image: home-${ENV_NUM}
    build:
      args:
        ARG1: "${ARG1}"
      ...
    labels:
      - traefik.http.routers.home.rule=Host(`${DOMAIN}`)
      ...
    env_file:
      - ${ENV}

文件prod.env:

DOMAIN = 'actualdomain.com'
ENV = 'prod.env'
ENV_NUM = 1
ARG1 = 'value 1'

文件dev.env:

DOMAIN = 'localhost'
ENV = 'dev.env'
ENV_NUM = 0
ARG1 = 'value 1'

然后运行:

  • docker-compose --env-file prod.env build,
  • docker-compose --env-file dev.env build

顺便说一句,即使到目前为止这个答案的大部分内容都在说明 ".env" 文件名和 env_file: 文件享有截然不同的语义……它们确实也可以按照 OP 的建议,以这种方式“很好地”组合以实现此用例。

顺便注意 docker-compose config 也适用于“调试”Compose 规范:

  • docker-compose --env-file prod.env config,
  • docker-compose --env-file dev.env config.

现在关于最后一个问题:

Getting the values from the prod.env or dev.env files to docker-compose is not the issue. The issue is getting those values to the Dockerfile.

首先可以注意到有两种不同的情况:

  1. 两个不同的部署环境(prod.envdev.env)可以共享同一个镜像,所以区别只在于运行时间环境变量(不是docker构建参数)。
  2. 或者,根据为 --env-file 传递的文件,图像应该不同(然后 docker-compose --env-file … build 确实是必要的)。

似乎大多数情况下,情况1.可以实现(而且问题的配置也是如此,因为ARG1值在prod.envdev.env) 并且为了再现性可以被视为 more-interesting(因为我们确信“prod”图像将与“dev”图像相同)。

然而,有时这样做是不可能的,我们属于“情况 2”。如果 Dockerfile 有一个特定的步骤,可能与测试等相关,则必须在生产模式下启用(resp.disabled)。

现在,让我们假设情况 2。我们如何将 --env-file 中的“所有内容”传递给 Dockerfile?只有一种解决方案,即扩展 docker-compose.ymlargs: 映射并包含您感兴趣的每个变量,例如:

services:
  home:
    image: home-${ENV_NUM}
    build: 
      context: .\home
      args:
        DOMAIN: "${DOMAIN}"
        ENV_NUM: "${ENV_NUM}"
        ARG1: "${ARG1}"
    networks:
      - demo-net
    env_file:
      - ${ENV}
    labels:
      - traefik.enable=true
      - traefik.http.routers.home.rule=Host(`${DOMAIN}`)
      - traefik.http.routers.home.entrypoints=web
    volumes:
      - g:\:c:\sharedrive

即使没有其他解决方案在构建时传递参数(从docker-compose到底层docker build -f Dockerfile …),这具有“声明性”的优点(仅在args: 将实际传递给 Dockerfile).

缺点?

我看到的唯一缺点是您在 运行 时可能有不需要的额外环境变量(从 docker-compose 到 underling docker run -e …), 例如 ENV=prod.env.

如果这是个问题,您可能需要像这样拆分 ".env" 文件:

文件prod.env:

DOMAIN = 'actualdomain.com'
ENV = 'prod-run.env'
ENV_NUM = 1
ARG1 = 'value 1'

文件prod-run.env:

DOMAIN = 'actualdomain.com'
ENV_NUM = 1

(假设你只想在运行时导出这两个环境变量)。

或者,为了更好地遵循通常的 Do-not-Repeat-Yourself 规则, 删除 prod-run.env,然后传递这些值为 docker-compose 构建参数,如前所述:

args:
  DOMAIN: "${DOMAIN}"
  ENV_NUM: "${ENV_NUM}"

写在Dockerfile:

ARG DOMAIN
ARG ENV_NUM

# ... and in the end:

ENV DOMAIN="${DOMAIN}"
ENV ENV_NUM="${ENV_NUM}"

我已经在 “实验 2”部分给出了这些 Dockerfile 指令的示例。

(抱歉,这个答案很长顺便说一句:)

我的解决方案很烦人,这就是为什么我花了这么长时间才弄明白的原因。 我的 dockerfile 在 windows 服务器上使用 powershell,所以我必须为每个参数执行此操作:

ARG ARG1
RUN echo $env:ARG1

这似乎相当小众,尤其是因为在 windows 服务器上使用 windows 容器不是我的第一选择,所以如果您对 env 文件等有疑问,请查看@ErikMD 的回答。