如何使用Dockerfile在容器中传递环境变量,但为环境变量(如.env

时间:2020-07-22 07:15:08

标签: docker docker-compose environment-variables dockerfile containers

所以我在这个问题上停留了很长时间。

我知道我们应该这样编写代码,以便在任何时候都可以使项目开源,而不会影响机密性。

因此,我似乎理解不建议直接在Dockerfile或docker-compose.yml中定义环境变量,而应该在一个单独的文件(例如 .env)中定义所有这些变量。 strong>。

现在,我知道在处理docker-compose时可以使用docker-compose -f docker-compose.yml --env-file=.env up

我想知道以下两件事:

  1. 如何使用Dockerfile从构建到运行(环境变量文件部分,例如--env-file
  2. 我们应该构建包含已经定义的环境变量的映像,然后与其他人共享该映像,以便在运行映像时不必因重新定义环境变量而烦恼它们,或者应该构建通用映像并然后让用户根据需要定义环境变量。什么是最佳做法?

有人也可以评论以下内容(使用Dockerfile)

方法1:

example_Dockerfile

FROM python:latest

# many other steps in between

RUN printf 'key=21345' > config

ENTRYPOINT ["./boot.sh"]

构建和运行的步骤:

  1. docker build -f example_Dockerfile -t my_container .
  2. docker run --env-file=example.env my_container

我知道方法1是正确的,但我想知道是否可以完成类似方法2的操作(如下)。

方法2:

example_Dockerfile

FROM python:latest

# many other steps in between 

RUN printf 'key=${KEY}' > config

ENTRYPOINT ["./boot.sh"]

example.env

# many other environment variables

KEY = 21345

如果您还可以提供命令步骤来构建并运行此方法中的容器来做到这一点,那就太棒了。

这两种方法的主要区别在于,在方法2中,我在 .env 文件中定义了 KEY 变量,并希望以某种方式将其集成到 Dockerfile (就像在 docker-compose.yml 中一样使用${KEY}),而在 Dockerfile 中没有提及该值

如果有人可以通过举例说明上述两点,我将不胜感激。

非常感谢高级

2 个答案:

答案 0 :(得分:0)

此用例有两个方面:

  • 如果仅需要构建环境变量,最好使用docker build arg。构建args与env vars相同,但仅在docker build期间,它们未打包在最终映像中。有关更多信息,请检查此link
    • docker build --build-arg VAR_A=val_a -f Dockerfile .
    • Dockerfile
      FROM python:3.7-slim
      ARG VAR_A
      RUN echo $VAR_A
      
  • 在某些情况下,环境var是通用的并且是运行时必需的,您可以使用Dockerfile中的ENV指令安全地打包它们

[Bonus Tip] -最好避免在Dockerfile中将latest图像标签与FROM指令一起使用,因为这会导致难以跟踪正在运行的图像版本回滚。

答案 1 :(得分:0)

我的一般经验是,最好使用环境变量进行设置(可以(或应该或必须)在运行时更改),但是比其他机制(如命令行参数或配置文件)更好。这包括诸如您依赖的其他服务的主机名之类的东西。典型的最小docker-compose.yml设置可能类似于:

version: '3.8'
services:
  app:
    build: .
    ports: ['8080:8080']
    environment:
      - REDIS_HOST=redis
      - PUBLIC_URL=http://example.com/
  redis:
    image: redis:6.0

我倾向于最小化Dockerfile中的环境变量。您暗示问题中存在一些保密问题;拥有图像的任何人都可以docker inspectdocker run --rm imagename env并将其转储出去,而不管它们是如何添加的;构建时的.env文件无济于事。在其他SO问题中,我看到了用于文件系统路径或端口的变量,但这些变量无需在运行时更改。

# Avoid setting filesystem paths or ports as environment variables:
ENV APP_DIR=/app
COPY . $APP_DIR
RUN chmod +x $APP_DIR/entrypoint.sh
ENV PORT=8080
EXPOSE $PORT
# It's fine to hard-code these values, and use relative paths:
WORKDIR /app
COPY . .
RUN chmod +x entrypoint.sh
EXPOSE 8080
# No matter what, the container will always be run as
docker run -v $PWD/data:/app/data -p 8080:8080 imagename
# and changing the build-time environment variables has no effect

从样式上讲,使Dockerfile尽可能简单(但没有更简单)会更好。如果您有带有固定键和固定值的配置文件,请不要尝试在Dockerfile中生成它。直接将其维护在磁盘上,如果对您而言有意义,则可以使用其他名称。

# Don't RUN echo ... > file; instead just
COPY config.docker.yaml ./config.yaml

另一面是不需要设置环境变量,因此您需要容忍一些未设置的事情。我发现有用的模式是猜测,如果一个程序直接运行,则是由开发人员手动配置的,如果它是在容器中运行,则诸如Compose或Kubernetes的自动化将提供环境变量。

# Check that $PUBLIC_URL is set
public_url = os.environ.get('PUBLIC_URL')
if public_url is None:
    print('Please set $PUBLIC_URL environment variable')
    os.exit(1)

# Get the Redis host, or assume a developer environment
redis_host = os.environ.get('REDIS_HOST', 'localhost')

我预先说过,与配置文件相比,我更喜欢直接使用环境变量。示例1和2的输出是相同的,因为配置文件在生成时是固定的。如果需要基于环境变量重新创建配置文件,则必须在容器启动时在入口点脚本中执行此操作。一个典型的示例可能使用GNU envsubst工具(不会在基于Alpine的映像上预安装)来重写它:

#!/bin/sh
# Rewrite the config file
envsubst < config.tmpl > config
# Switch over to the main container process
exec "$@"
# ENTRYPOINT _must_ be JSON-array syntax for this
ENTRYPOINT ["./entrypoint.sh"]
CMD ["./boot.sh"]

envsubst替换了文件中的外壳样式$ENVIRONMENT_VARIABLE引用,因此此处的config.tmpl文件看起来像

key=$KEY