通过 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_file
s 行只是为基础 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
.
首先可以注意到有两种不同的情况:
- 两个不同的部署环境(
prod.env
和dev.env
)可以共享同一个镜像,所以区别只在于运行时间环境变量(不是docker构建参数)。
- 或者,根据为
--env-file
传递的文件,图像应该不同(然后 docker-compose --env-file … build
确实是必要的)。
似乎大多数情况下,情况1.可以实现(而且问题的配置也是如此,因为ARG1
值在prod.env
和dev.env
) 并且为了再现性可以被视为 more-interesting(因为我们确信“prod”图像将与“dev”图像相同)。
然而,有时这样做是不可能的,我们属于“情况 2”。如果 Dockerfile
有一个特定的步骤,可能与测试等相关,则必须在生产模式下启用(resp.disabled)。
现在,让我们假设情况 2。我们如何将 --env-file
中的“所有内容”传递给 Dockerfile
?只有一种解决方案,即扩展 docker-compose.yml
的 args:
映射并包含您感兴趣的每个变量,例如:
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 的回答。
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
首先,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_file
s 行只是为基础 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-argARG3
的值不会在 运行 时间传播(参见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
ordev.env
files todocker-compose
is not the issue. The issue is getting those values to theDockerfile
.
首先可以注意到有两种不同的情况:
- 两个不同的部署环境(
prod.env
和dev.env
)可以共享同一个镜像,所以区别只在于运行时间环境变量(不是docker构建参数)。 - 或者,根据为
--env-file
传递的文件,图像应该不同(然后docker-compose --env-file … build
确实是必要的)。
似乎大多数情况下,情况1.可以实现(而且问题的配置也是如此,因为ARG1
值在prod.env
和dev.env
) 并且为了再现性可以被视为 more-interesting(因为我们确信“prod”图像将与“dev”图像相同)。
然而,有时这样做是不可能的,我们属于“情况 2”。如果 Dockerfile
有一个特定的步骤,可能与测试等相关,则必须在生产模式下启用(resp.disabled)。
现在,让我们假设情况 2。我们如何将 --env-file
中的“所有内容”传递给 Dockerfile
?只有一种解决方案,即扩展 docker-compose.yml
的 args:
映射并包含您感兴趣的每个变量,例如:
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 的回答。