当将现代应用程序部署到生产环境中,使用容器化技术已成为一种常见的做法。Docker,作为一种流行的容器化平台,为开发者提供了一种便捷的方式来封装应用程序及其依赖,从而实现一致性和可移植性。对于基于 Next.js 构建的应用程序,编写优化的 Dockerfiles 是确保应用在容器环境中高效运行的关键一步。通过合理的镜像构建流程,可以最大程度地减小镜像的大小,提升应用的性能和可维护性。
有时候编写Dockerfile可能会有些棘手。我们需要注意我们推送的文件和我们分配的权限。
构建镜像中最具挑战性的事情之一就是保持镜像的大小。一个写得好的 Docker 镜像占用的空间尽可能小。这可以通过多种方式来确保:
- 我们可以决定限制我们推送到图像的文件。这涉及到过滤掉不需要的文件。
- 在生成镜像时使用多阶段构建。
- 在使用案例耗尽后,删除不需要的文件。
多阶段构建是一种在Docker中使用的技术,旨在优化镜像构建过程。它允许我们在一个Dockerfile中定义多个构建阶段,每个阶段都可以使用不同的基础镜像和构建步骤。 使用多阶段构建的主要优势是减小最终镜像的大小。通过在不同的阶段中只包含必要的构建工具和依赖项,我们可以避免将不必要的文件和库打包到最终的镜像中。这样可以显著减少镜像的体积,提高部署效率。 另一个优势是提高构建速度。由于每个阶段都可以并行执行,我们可以在不同的阶段中同时进行构建,从而加快整个构建过程。这对于大型项目或需要频繁构建的应用程序特别有用。 多阶段构建还可以帮助我们更好地组织和管理构建过程。通过将构建步骤分解为多个阶段,我们可以更清晰地了解每个阶段的作用和目的。这样可以使构建过程更可靠、可维护和可扩展。 总之,多阶段构建是一种强大的技术,可以帮助我们优化镜像构建过程,减小镜像大小,提高构建速度,并更好地组织和管理构建过程。它是在Docker中构建高效和可靠镜像的重要工具
多阶段构建是一种非常流行的方法,可以减小镜像的大小。要编写一个真正高效的Dockerfile,您需要使用shell技巧和其他逻辑,尽量保持每个层尽可能小,并确保每个层都具有来自前一个层的所需构件,而不包含其他多余的内容。
多阶段构建使用多个 FROM
语句,可以与不同的基础镜像一起使用,并且每个语句都开始了构建的新阶段。您可以选择性地从一个阶段复制构件到另一个阶段,将不需要的内容留在最终镜像之外。从而确保最终镜像尽可能精简。可以使得容器化的 Next.js 应用既高效又稳定地运行。
Next.js中的独立应用程序
在我们开始编写Dockerfile之前,我们需要了解Next框架的一个非常强大的功能。
理想情况下,当我们在Next中构建一个应用程序时,我们会继续依赖于node_modules,并进而依赖于package.json。这意味着即使在最终的镜像中,我们也需要导入所有的依赖项,以确保应用程序能够成功运行。
这就是Output File Tracing/ Standalone在NextJS中发挥作用的地方。Next.js可以自动创建一个独立的文件夹,仅复制生产部署所需的必要文件,包括node_modules中的选择文件。
要利用这个自动复制功能,您可以在您的 next.config.js 文件中启用它。
这将在 .next/standalone
创建一个文件夹,可以独立部署而无需安装 node_modules
。
此外,还提供了一个最小的 server.js
文件,可以用来替代 next start
。
这个最小服务器默认不会复制 public
和 .next/static
文件夹。可以手动将这些文件夹复制到 standalone/public
和 standalone/.next/static
文件夹中,之后 server.js
文件将自动为其提供服务。
Dockerfile
让我们一步一步地构建Dockerfile:
- 让我们创建我们的第一层 -
dependencies
,在这里我们将解决所有的依赖关系并定义一个基础。您可以从可用列表中选择自己的基础。
vbnet
FROM node:16-alpine AS dependencies
我们来添加一个本地包,设置工作目录,并复制 package.json
和 package-lock.json
来定义依赖项。
sql
RUN apk add --no-cache libc6-compat
WORKDIR /home/app
COPY package.json ./
COPY package-lock.json ./
一旦我们准备好了,我们就可以继续安装所需的依赖项。
css
RUN npm i
我们已经添加了所有的依赖项。我们的第一层已经准备好了。
我们来创建第二层------ builder
。这一层的目标是生成我们最终要推送到最终镜像中的静态文件。
vbnet
FROM node:16-alpine AS builder
接下来,我们需要将前一层的相关工件复制到 builder
中,并设置当前工作目录。
bash
WORKDIR /home/app
COPY --from=dependencies /home/app/node_modules ./node_modules
COPY . .
我们现在想要构建这个应用程序。
arduino
RUN npm run build
这标志着第二层的结束。在这个阶段,我们拥有了静态文件,我们将把它们推送到我们的最终镜像中。
让我们创建我们的第三个也是最后一个层级 --- runner
。在这里,我们将推送静态文件,并尽量保持图像的大小较小。
我们还设置了工作目录,并将 NEXT_TELEMETRY_DISABLED
设置为 true
。这将确保我们选择退出Next的匿名数据收集。
bash
FROM mhart/alpine-node:slim-14 AS runner
WORKDIR /home/app
ENV NEXT_TELEMETRY_DISABLED 1
然后我们将相关的工件放入图像中。这涉及到来自 .next/standalone
的独立文件、公共文件和静态文件。
vbnet
COPY --from=builder /home/app/.next/standalone ./standalone
COPY --from=builder /home/app/public /home/app/standalone/public
COPY --from=builder /home/app/.next/static /home/app/standalone/.next/static
最后,我们暴露所需的端口,并记录下最终的命令来启动应用程序。
yaml
EXPOSE 3000
ENV PORT 3000
CMD [ "node" , "./standalone/server.js" ]
我们的Dockerfile大致如下:
vbnet
FROM node:16-alpine AS dependencies
RUN apk add --no-cache libc6-compat
WORKDIR /home/app
COPY package.json ./
COPY package-lock.json ./
RUN npm iFROM node:16-alpine AS builder
WORKDIR /home/app
COPY --from=dependencies /home/app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
ARG NODE_ENV
ENV NODE_ENV="${NODE_ENV}"
RUN npm run buildFROM mhart/alpine-node:slim-14 AS runner
WORKDIR /home/app
ENV NEXT_TELEMETRY_DISABLED 1
COPY --from=builder /home/app/.next/standalone ./standalone
COPY --from=builder /home/app/public /home/app/standalone/public
COPY --from=builder /home/app/.next/static /home/app/standalone/.next/static
EXPOSE 3000
ENV PORT 3000CMD [ "node" , "./standalone/server.js" ]
结论
当我们为像Next这样的框架编写Dockerfile时,最常见的错误是:
- 将整个构建文件推送到容器中
- 将所有依赖项安装或复制到最终镜像中。
- 将所有的工件复制到最终层,包括源文件。
在我们决定将每个构建文件(无论是哪个框架)推送到镜像之前,我们需要了解其相关性。
通过对 next.config.js
和 Dockerfile
进行这些更改,我成功将Docker镜像的大小从1.45 GB降低到仅有81.6 MB。
Size of the container with .next:
Size of the container with standalone: