Docker 从文件中编写构建时间参数

Docker compose build time args from file

我知道可用的变量替换,我可以在项目的根目录使用 .env 并且可以完成,但在这种情况下,我正在调整现有项目,其中现有 .env 个文件位置是预期的,我想避免在多个文件上有 var 条目!

有关详细信息,请参阅 documentation,并且所有代码都可以在回购的 docker-support 分支上以 WIP 形式提供,但我将在下面简要描述项目和问题:

项目结构

|- root
|  |- .env # mongo and mongo-express vars (not on git!)
|  |- docker-compose.yaml # build and ups a staging env
|  |- docker-compose.prod.yaml # future wip
|  |- api # the saas-api service
|     |- Dockerfile # if 'docked' directly should build production
|     |- .env # api relative vars (not on git!)
|  |- app # the saas-app service
|     |- Dockerfile # if 'docked' directly should build production
|     |- .env # api relative vars (not on git!)

或者查看整个 here,顺便说一句,目前效果很好,但是 saas-app 在为 staging/production 构建图像时存在一个问题,我可以识别到目前为止。

问题

在构建时 Next.js 使用 webpack 构建页面的静态版本来完成 process.env 替换,因此它需要实际最终的 运行 变量包含在docker 构建阶段,因此 next.js 不需要在运行时再次重建,这样我就可以在流量需要时安全地生成多个实例!

我知道如果在运行时没有发送相同的变量,它将不得不再次重建,违背了这个练习的要点,但这正是我在这里试图防止的,如果错误的值发送给我们而不是项目!

我还需要考虑 Next.js BUILD ID 管理,但那是另一个 time/question。

尝试次数

我一直在测试,包括应用程序预期的每个变量的 ARG 和 ENV 声明 Dockerfile,例如:

ARG GA_TRACKING_ID=
ENV GA_TRACKING_ID ${GA_TRACKING_ID}

这按预期工作,但它迫使我在 docker-compose.yml 文件中手动声明它们,这并不理想:

  saas-app:
    build:
      context: app
      args:
        GA_TRACKING_ID: UA-xXxXXXX-X

我不能在这里使用变量替换,因为我的根 .env 不包含这个变量,它在 ./app/.env 上,我还测试了将值留空但它没有从env_fileenviroment 定义,我相信这符合预期。

我已经 pastbinned 完整输出了 docker-compose config 以及存储库中的现有版本:

理想情况下,我想要:

  saas-app:
    build:
      args:
        LOG_LEVEL: notice
        NODE_ENV: development
        PORT: '3000'
      context: /home/pedro/src/opensource/saas-boilerplate/app
    command: yarn start
    container_name: saas-app
    depends_on:
    - saas-api
    environment:
      ...

成为:

  saas-app:
    build:
      args:
        LOG_LEVEL: notice
        NODE_ENV: development
        PORT: '3000'
        BUCKET_FOR_POSTS: xxxxxx
        BUCKET_FOR_TEAM_AVATARS: xxxxxx
        GA_TRACKING_ID: ''
        LAMBDA_API_ENDPOINT: xxxxxxapi
        NODE_ENV: development
        STRIPEPUBLISHABLEKEY: pk_test_xxxxxxxxxxxxxxx
        URL_API: http://api.saas.localhost:8000
        URL_APP: http://app.saas.localhost:3000
      context: /home/pedro/src/opensource/saas-boilerplate/app
    command: yarn start
    container_name: saas-app
    depends_on:
    - saas-api
    environment:
      ...

问题

如果可能的话,我怎样才能做到这一点,但是:

  1. 无需将现有的 .env 文件合并到一个根目录中,也无需在多个文件上复制变量。
  2. 无需在撰写文件中手动声明值,也无需在命令中推断它们,例如docker-compose build --build-arg GA_TRACKING_ID=UA-xXxXXXX-X?
  3. build stage 期间不必 COPY 每个 .env 文件,因为它感觉不对 and/or 安全?
  4. 也许 args_file 对撰写团队的撰写 build 选项功能请求在我看来是有效的,你也这么说吗?
  5. 或者也许在撰写文件上有一个根选项,您可以在其中设置多个 .env 文件用于变量替换?
  6. 或者我没有看到的另一种解决方案?有什么想法吗?
  7. 我不介意将每个 .env 文件作为 config or secret 发送,这是比拆分撰写文件更干净的解决方案,有人 运行 这样的生产示例吗?

与其尝试在多个 .env 中传递和合并值,不如考虑制作一个主 .env 并让 API 和 APP 服务继承相同的根 .env?

我已经设法达成妥协,不会影响任何现有的开发工作流程,也不允许 app 在没有环境变量的情况下构建(这一要求将对于生产构建更为重要)。

我基本上决定重用 docker 的内部能力来读取 .env 文件并在撰写文件的变量替换中使用这些能力,这是一个例子:

# compose
COMPOSE_TAG_NAME=stage

# common to api and app (build and run)
LOG_LEVEL=notice
NODE_ENV=development
URL_APP=http://app.saas.localhost:3000
URL_API=http://api.saas.localhost:8000
API_PORT=8000
APP_PORT=3000

# api (run)
MONGO_URL=mongodb://saas:secret@saas-mongo:27017/saas
SESSION_NAME=saas.localhost.sid
SESSION_SECRET=3NvS3Cr3t!
COOKIE_DOMAIN=.saas.localhost
GOOGLE_CLIENTID=
GOOGLE_CLIENTSECRET=
AMAZON_ACCESSKEYID=
AMAZON_SECRETACCESSKEY=
EMAIL_SUPPORT_FROM_ADDRESS=
MAILCHIMP_API_KEY=
MAILCHIMP_REGION=
MAILCHIMP_SAAS_ALL_LIST_ID=
STRIPE_TEST_SECRETKEY=
STRIPE_LIVE_SECRETKEY=
STRIPE_TEST_PUBLISHABLEKEY=
STRIPE_LIVE_PUBLISHABLEKEY=
STRIPE_TEST_PLANID=
STRIPE_LIVE_PLANID=
STRIPE_LIVE_ENDPOINTSECRET=

# app (build and run)
STRIPEPUBLISHABLEKEY=
BUCKET_FOR_POSTS=
BUCKET_FOR_TEAM_AVATARS=
LAMBDA_API_ENDPOINT=
GA_TRACKING_ID=

查看更新后的 docker-compose.yml I've also made use of Extension fields 以确保在构建和 运行.

中只发送正确且有效的变量

它打破了问题中的规则 1.,但我觉得这是一个足够好的折衷方案,因为它不再依赖于其他 .env 文件,而这些文件在大多数情况下可能是开发密钥!

不幸的是,如果 vars 将来发生变化,我们将需要维护 compose 文件,并且相同的 .env 文件必须用于生产构建,但由于这可能会在某些外部完成CI/CD,那个不用太担心。

我发布了这个但并没有完全结束这个问题,如果其他人可以提出更好的想法,我将不胜感激。