[译]揭开 Docker for Javascript 的神秘面纱

用于 JavaScript 应用程序的 Dockerfile 的范围可以很大------从两行到五十行。这是怎么回事?这种复杂性会让一些开发人员无法真正理解这个强大的工具,所以今天,我想通过解读一个用于 JavaScript 应用程序的示例Dockerfile 来揭开 Docker 的神秘面纱。无论您使用什么JS框架,这都应该是一个有用的资源。

虽然本文不会对该主题进行全面的教育,但在本文结束时,您将对编写和修改任何Dockerfile以满足应用程序需求的能力更加自信。

Docker 快速入门

一些专属名词:

  • Image(镜像) :这包含您的应用程序代码和运行它所需的一切。在大多数情况下,Docker 镜像(或者更具体地说,)只是文件系统层的堆栈。您可以使用 docker build 或 等其他工具构建新镜像。要了解更多信息,请查看

  • Container(容器) :这是实际运行你的映像并使你的应用程序变焦的东西。你可以使用docker run运行现有的Docker映像。设置容器通常是定义环境变量、公开哪些端口、允许哪些协议等的地方。

  • Instructions(指令):这些是ALLCAPS中每行开头的位,后跟任意数量的参数。您可能会听到人们称它们为命令或语句,但它们正式称为指令。

  • Layers(层):Dockerfile中的几乎每条指令行都变成了一个层。这些层是构成Docker映像的tarball。此外,这些层的顺序很重要,尤其是对于构建优化。

让工作更轻松:使用 Dockerfile 生成器

我要取巧一点,并提早告诉你,虽然对 Dockerfile 语法有更深入的理解非常有用,但有一个更简单的方法 (至少对JavaScript开发人员来说!),这就是使用 Fly.io的Dockerfile生成器

bash 复制代码
$ npx @flydotio/dockerfile@latest
$ npx dockerfile

该软件包可以与npmbun.sh/一起使用(您可以使用bunx而不是npx来运行带有bun的脚本)。根据需要调整Dockerfile有额外的参数;请务必查看README以获取更多详细信息。

剖析 Dockerfile

今天我们将剖析Next. JS应用程序的Dockerfile,因为它涵盖了有效使用Dockerfile的所有不同方式。这将有助于解释Dockerfile的不同部分,即使您不使用Next.js应用程序。

这是我们将处理的Dockerfile。

dockerfile 复制代码
# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=20.9.0
FROM node:${NODE_VERSION}-slim AS base

LABEL fly_launch_runtime="Next.js"

# Next.js app lives here
WORKDIR /app

# Set production environment
ENV NODE_ENV="production"

# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build node modules
RUN apt-get update -qq && \
    apt-get install -y build-essential pkg-config python-is-python3

# Install node modules
COPY --link package-lock.json package.json ./
RUN npm ci --include=dev

# Copy application code
COPY --link . .

# Build application
RUN npm run build

# Remove development dependencies
RUN npm prune --omit=dev

# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD [ "npm", "run", "start" ]

这可能看起来像是很多步骤。但是每一行都有原因,阅读到这篇文章的结尾,你就会明白每一行都做了什么以及为什么要使用它。

让我们从高级概述开始。我们的Dockerfile可以分为三个阶段:

  1. 设置我们的基本映像(剧透:它只是Node)

  2. 构建应用程序

  3. 将我们的应用程序代码添加到我们的原始基础镜像

您会注意到这些阶段中的每一个都以一个FROM语句开始,该语句设置一个基本图像。FROM语句用于设置一个基本图像,关于它们有一些需要注意的事情:

  • 每个Dockerfile必须至少包含_一个_ FROM指令

  • 可以有多个FROM指令

  • 最后一个 FROM指令总是获胜*-获胜,我的意思是在最终图像中使用。在它被扔掉之前的一切

您可能希望拥有多个FROM语句的原因是为了优化最终图像的大小。这将我们带到Dockerfile构建的一个重要主题:多阶段构建。

*此行为可以被覆盖时,部署您的应用程序与fly deploy ---build-target=<specific target>

多阶段构建:它们是什么以及为什么使用它们

在Dockerfiles中使用多个FROM语句允许我们将构建过程分解为块,并最终使最终映像尽可能小。

打个比方,让我们假设你在做一批蔬菜高汤。非常简单:把一堆蔬菜残渣放在水里炖一个小时左右,瞧!你有高汤了。现在,正如你所料,你必须在最后过滤蔬菜;否则,你只是做了奇怪的汤。但是!仅仅因为最终产品不含任何蔬菜并不意味着它们对这个过程不重要。

这就是多阶段Docker构建的主要优势。你从不同的图像中借用你需要的东西来做重要的工作,并在你的最终图像中使用结果,扔掉其余的。你的图像越小,你的应用程序启动得越快。

现在我们对Docker构建的整体流程有了一个高级概述,以下是每个阶段发生的事情。

第一阶段:Base

ARG

dockerfile 复制代码
ARG NODE_VERSION=20.9.0

我们通过定义我们想要使用的Node(或Bun!或其他任何东西)版本来开始我们的文件。很简单,但是ARG命令到底是什么?

在Dockerfiles中,有两种设置变量的方法:

  1. ARG-这些用于设置构建时变量。正如您所料,这些变量在构建时可用(但不是运行时)

  2. ENV-这些变量在_构建_和_运行时都可用于您的应用_

**不要将敏感数据存储在Dockerfile中。**它们可以安全地用于NODE_VERSIONNODE_ENV之类的东西,但是像令牌、数据库URL或其他机密之类的东西应该以不同的方式处理。

要处理构建时机密信息 ,您需要使用RUN指令挂载它们。您可以使用前面提到的@flydotio/dockerfile包来执行此操作,然后,当您部署到Fly.io时,设置构建机密的值(第二个命令):

bash 复制代码
$ npx dockerfile --mount-secret=MY_SECRET
$ fly deploy --build-secret MY_SECRET=<value>

为了处理运行时机密,这些机密应该远离您的Dockerfile,而是使用以下命令设置:

bash 复制代码
$ fly secrets set SECRET_PASSWORD=<value>

当应用程序在生产环境中运行时,这些秘密会作为环境变量公开

FROM

dockerfile 复制代码
FROM node:${NODE_VERSION}-slim as base

在本例中,node图像的 名称FROM``:<version>-slim是一个标记,用于表示基础图像的特定_版本_。

我如何知道我的应用需要使用什么Debian版本?

对于大多数使用节点或Bun的应用程序,**我们建议从-slim变体开始。**这采用了Debian的最新版本,并删除了您不需要的所有内容。如果您确实需要一些被删除的内容,您可以稍后使用apt-get将其添加回构建阶段(我们将在构建阶段部分中介绍)。

LABEL

dockerfile 复制代码
LABEL fly_launch_runtime="Next.js"

LABEL允许您为Docker图像和容器设置任意键值元信息。如果这些信息对您的自动化有用,它允许您注释任何您想要的信息。这是我们的框架团队用来跟踪常用框架的Fly.io特定标签/标记,因此我们知道在开发新功能时应该优先考虑什么。它在技术上是可选的,但它确实有助于我们满足您首选框架的需求!😄

WORKDIR

dockerfile 复制代码
WORKDIR /app

WORKDIR指令设置任何后续RUNCOPYADD语句的当前工作目录。这是构建应用程序代码的地方,也是部署到生产环境中的文件夹。

ENV

dockerfile 复制代码
ENV NODE_ENV="production"

ENV指令设置环境变量,这些变量在_构建时和运行时都可用。_

请记住,您的Dockerfile是源码。源码绝不能包含秘密信息。如果您需要将秘密信息设置为环境变量,请使用Fly.io secrets

bash 复制代码
$ fly secrets set SECRET_KEY=<value>

第二阶段:build

正如我们前面所讨论的,每次遇到FROM语句时,您都知道您已经到达了Docker构建的新阶段。让我们看看Dockerfile的第二阶段。

dockerfile 复制代码
FROM base AS build

这个FROM...AS...标志着我们第二阶段的开始。如果你还记得,我们的第一阶段始于node:<version>-slim,我们_将其命名为_ base。现在在第二阶段,我们正在复制base并将其命名为build。从现在开始,任何build _都不会影响原始base。_稍后你会看到,这允许我们只挑选我们想要保留的部分,然后扔掉其余的部分。build

RUN

dockerfile 复制代码
RUN apt-get update -qq \
 && apt-get install -y build-essential pkg-config python-is-python3

现在我们已经建立了一个base作为build的副本,我们将开始做一些_实际_的构建工作。首先,让我们了解RUN指令的用途,然后我们将讨论这个apt-get命令。

RUN语句用于运行命令。令人震惊,我知道。但值得注意的是,这不是运行shell命令的唯一方法。实际上有三种常用指令用于此类任务:

  1. RUN:总是创建一个新层,因此最好将这些命令链接成一条指令,就像我们上面对多个apt-get命令所做的那样。一般来说,RUN非常适合应用程序代码的_设置_。

  2. ENTRYPOINT:这将设置首先在容器中运行的进程。这通常不是您的Web服务器。默认切入点是/bin/sh -c,它将启动shell进程,但可以使用ENTRYPOINT指令进行自定义。然后,您使用CMD指令设置的任何内容都将作为参数传递给该shell进程(例如启动服务器的命令)。

  3. CMD:此指令设置传递到入口点的默认命令。这通常是您编写命令以启动Web服务器的地方,例如CMD ["npm", "run", "start"]

你可能需要知道,它们之前的区别:

dockerfile 复制代码
<INSTRUCTION> npm install
dockerfile 复制代码
<INSTRUCTION> ["npm", "install"]

不同之处在于,当您使用字符串参数参数时,Docker将在shell中运行您的命令(更具体地说,在/bin/sh -c中)。如果您使用字符串数组,它将直接运行程序,而无需将其包装在shell中。在许多情况下,这并不重要,但有时在非常不常见的用例中可能很重要,例如当容器图像中没有操作系统时,或者当每千字节的RAM都很重要时。

apt-get 是什么,为什么需要它?

工具apt-get用于安装和管理基于Debian的Linux包(想想NPM,但对于操作系统包;它类似于macOS上的homebrew)。我们包含的包(build-essentialpkg-configpython-is-python3)是许多JavaScript包的常见要求。可能需要实验来找到您的应用程序所需的确切依赖项集,但即使您认为您的应用程序不需要它们,也可以安全地保留这条线用于未来的开发,而不必担心膨胀,因为这些不会包含在您的最终图像中。

COPY

dockerfile 复制代码
COPY --link package-lock.json package.json ./

安装任何Linux包要求后,我们终于可以开始复制部分应用程序代码了。COPY指令将存储库中的本地文件复制到Docker图像上的某个位置。

什么是 --link

通常,如果COPY之前的层有新的更改,则需要重新运行COPY语句。请记住,几乎每条Docker指令都会创建一个新的,而层就是构成Docker图像的tarball。因为这些层需要_分层_,所以会运行diff以查看前一层是否有任何可能影响当前层的更改。如果前一层_有_更改,它将使后续层无效。

但是,通过包含--link我们创建了一个新层 ,当对前面的图像进行更改时,它不会失效,从而允许我们缓存--link层。

要了解更多信息,请查看这篇文章,其中包含一个非常有用的信息图来说明COPYCOPY --link之间的区别。


我们build步骤的最后几行现在应该感觉更熟悉了。此时,我们只是简单地安装其余的依赖项,复制我们的应用程序代码,并去掉所有的devDependencies,以便我们的代码可以投入生产。

dockerfile 复制代码
RUN npm ci --include=dev

# Copy application code
COPY --link . .

# Build application
RUN npm run build

# Remove development dependencies
RUN npm prune --omit=dev

第三阶段:base + build

dockerfile 复制代码
# Final stage for app image
FROM base AS runner

# Copy built application
COPY --from=build /app /app

我们在最后阶段!因为我们已经到达了文件中的最终FROM,我们知道base将是我们最终图像的目标。

复制 --from=<target>

您会注意到我们的COPY语句包括--from=build。**这就是使用多阶段构建的魔力。**在这里,我们从build目标中挑选部分,并留下运行时不需要的任何东西。这使我们的最终图像尽可能小!


EXPOSE

dockerfile 复制代码
EXPOSE 3000
CMD [ "npm", "run", "start" ]

我们已经完成了Dockerfile!我们在关于RUN语句的章节中简要讨论了CMD语句,现在我们可以看到它的实际应用。如前所述,在启动Web服务时,CMD通常是用于指定启动过程的指令。

然而,就在我们的start命令之前,我们使用EXPOSE指令指定我们希望Docker容器公开的内部端口。这里的关键词是_内部_------对于Web服务,指定的端口被映射到外部端口80或443以接受HTTP(S)请求。

部署到Fly.io时的注意事项:此内部端口也设置在fly.toml中。此处指定的任何端口都将覆盖您在EXPOSE中设置的端口:

lua 复制代码
[http_service]
  internal_port = 8080

结论

希望到现在,您开始对自己对Dockerfiles的理解感到更深刻。

原文

相关推荐
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man1 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
cs_dn_Jie1 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿2 小时前
webWorker基本用法
前端·javascript·vue.js
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
清灵xmf3 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据3 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫