如何将 Next.js 应用程序 Docker 化
这是从我的博客转载的文章:https://www.vorillaz.com/nextjs-docker/。
Next.js 就像是构建 React 应用的瑞士军刀。它让开发过程流畅无阻,提供卓越的性能和一流的开发者体验。但是,当需要将自己构建的应用部署到主流部署服务商之外的平台时,情况就会变得有些棘手。你需要管理依赖项、配置环境,并确保应用在不同平台上运行一致。
现在,Docker 就派上用场了。我们将把你的 Next.js 应用、它的运行环境以及所有依赖项打包到一个小型容器里。你可以把容器想象成运行在精简版 Linux 系统上的应用。应用的核心部件叫做 Docker 镜像——它是容器的蓝图。而容器本身呢?它就是基于这个蓝图运行的应用。
为了实现这一神奇功能,我们使用了一种声明式方法,即 Dockerfile。这就像给 Docker 一个待办事项清单。Docker 随后会读取并执行该文件中的指令,从而构建和部署您的应用程序。
为什么你应该使用 Docker 将你的 Next.js 应用容器化
您可能出于多种原因想要考虑使用 Docker 将 Next.js 应用程序容器化。以下列举了一些优势:
-
跨环境行为一致:Docker 容器确保您的 Next.js 应用在不同的平台和环境中行为一致。
-
轻松部署和扩展: Docker 可让您轻松地将 Next.js 应用部署和扩展到多个实例。这使得应对流量高峰和满足高可用性要求变得更加容易。
-
可移植性: Docker 容器具有可移植性和可共享性。您可以与团队成员无缝协作,或将应用程序部署到不同的环境中,而无需手动配置。
-
经济高效的基础设施:管理您自己的部署环境可以降低成本。Docker 允许您在单台机器上自行托管多个隔离的 Next.js 应用程序,或者跨不同的托管服务商迁移,从而节省时间和资源。
-
简化依赖管理:将应用程序的所有依赖项打包在 Docker 容器中,无需在主机环境中手动安装和管理。
Next.js 应用容器化:分步指南
安装 Docker。
首先,你需要确保你的机器上已经安装了 Docker。安装完成后,运行 Docker 服务并确保一切运行正常。你可以通过在终端运行一个简单的命令来检查。
❏ ~ docker info
Client:
Version: 24.0.7
Context: orbstack
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.12.0
编辑next.config.js文件
要将 Next.js 应用构建为容器,您需要更改项目的配置。
Next.js 可以自动创建一个独立文件夹,其中仅复制生产部署所需的必要文件,包括您的所有node_modules.
/** @type {import('next').NextConfig} */
module.exports = {
output: "standalone",
};
创建.dockerignore文件
我们需要创建一个.dockerignore文件,因为我们不希望将不必要的文件传输到容器中,从而增加 Docker 镜像的大小。您可以.dockerignore在项目根目录下创建一个文件,并添加以下内容:
.dockerignore
# Not needed as we are bundling the whole app
node_modules
# Log files
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
.next
.git
创建 Dockerfile
现在你可以创建一个 Dockerfile 来描述镜像的构建方式。我们将使用pnpmpackage-manager,但你也可以使用npmpip 或yarnapt。DockerfileDockerfile将放置在项目根目录下,内容如下:
FROM node:20-alpine AS base
### Dependencies ###
FROM base AS deps
RUN apk add --no-cache libc6-compat git
# Setup pnpm environment
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prefer-frozen-lockfile
# Builder
FROM base AS builder
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
### Production image runner ###
FROM base AS runner
# Set NODE_ENV to production
ENV NODE_ENV production
# Disable Next.js telemetry
# Learn more here: https://nextjs.org/telemetry
ENV NEXT_TELEMETRY_DISABLED 1
# Set correct permissions for nextjs user and don't run as root
RUN addgroup nodejs
RUN adduser -SDH nextjs
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
# Exposed port (for orchestrators and dynamic reverse proxies)
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ "wget", "-q0", "http://localhost:3000/health" ]
# Run the nextjs app
CMD ["node", "server.js"]
让我们来详细分析一下。我们将构建过程分成多个步骤,以便利用 Docker 的缓存机制。我们的基础镜像node:20-alpine比较小。在每个步骤中,我们都会指示 PNPM 缓存模块安装。
在这deps一步中,我们将打包依赖项。我们需要将 `.conf`package.json和pnpm-lock.yaml`.lock` 文件移动到工作目录,并安装项目的所有依赖项。由于 Next.js 会自动打包生产环境的依赖项,因此我们无需devDependencies单独安装生产环境的依赖项。
由于 Docker 引擎会对其进行内部缓存,因此这一构建步骤将显著加快我们的构建过程。此外,pnpm 的符号链接策略和内部缓存机制将使我们的模块以极快的速度安装。
接下来是构建步骤。切换到工作目录后,我们将复制上node_modules一步的文件,然后复制整个项目并运行pnpm build。此操作将触发我们在 .htaccess 文件中定义的脚本package.json,之后我们就可以进行最后一步了。
这一runner步骤最终将创建我们的 Docker 镜像。我们需要设置一个权限受限的用户和用户组,因为我们不希望root在容器内以管理员身份运行应用程序。完成后,我们将从上一步复制 `<path>`、`<group>` 和 `<packet>` 文件夹/.next/standalone。Next.js.next/static的独立模式会将所有内容打包到一个独立的包中,因此我们无需担心复制任何其他文件夹。请注意,还有一个文件可用于区分不同的构建版本。我们还需要设置环境变量,因为许多 npm 模块都严重依赖于此设置。/publicbuilder.next/BUILD_IDNODE_ENVproduction
最后,我们将暴露 3000 端口,因为这是 Next.js 默认监听的端口。此外,我们还添加了一个健康检查,即 ping 我们的/healthAPI 路由。这一步骤将确保容器在启动 Web 服务器之前已准备就绪。之后,我们将通过执行以下命令来运行服务器CMD ["node", "server.js"]。
构建镜像并运行容器
如果你按照本教程操作,现在就可以开始构建你的 Next.js 应用程序了。
打开终端,运行以下命令构建镜像:
❏ ~ docker build -t my-nextjs-docker .
完成后,您可以尝试重新运行构建过程并获取构建日志,您会发现所有步骤都已在内部缓存,构建过程只需几秒钟。
❏ ~ docker build -t my-nextjs-docker .
[+] Building 2.6s (25/25) FINISHED docker:orbstack
=> [internal] load build definition from Dockerfile 0.0s
=> transferring context: 225B 0.0s
=> [internal] load metadata for docker.io/library/node:20-alpine 2.6s
=> CACHED [runner 1/7] RUN addgroup nodejs 0.0s
=> CACHED [runner 2/7] RUN adduser -SDH nextjs 0.0s
=> CACHED [runner 3/7] RUN mkdir .next
您还可以使用命令查找有关图像的建筑物详细信息docker images。
❏ ~ docker images my-nextjs-docker
REPOSITORY TAG IMAGE ID CREATED SIZE
--
my-nextjs-docker latest 09462a81711b 14 hours ago 155MB
现在终于到了启动容器的时候了,我们需要将端口 3000 绑定到容器并启动镜像。
❏ ~ docker run -p 3000:3000 my-nextjs-docker
终端输出将显示应用程序正在运行。
❏ ~ docker run -p 3000:3000 my-nextjs-docker
▲ Next.js 14.0.4
- Local: http://localhost:3000
- Network: http://0.0.0.0:3000
✓ Ready in 39ms
现在您可以前往http://localhost:3000,然后会看到 Next.js 的默认主页。
环境因素
Next.js 提供了一种通过文件管理环境变量的可靠方法.env。您可以创建单独的.env文件,文件后缀决定了设置的环境。请注意,部署环境实际上并不绑定到NODE_ENV环境变量。即使我们在本地运行容器,staging该NODE_ENV变量也会被设置为production.
首先,我们需要创建三个示例文件,每个部署环境一个(dev,,staging)production。
❏ ~ touch .env.local.sample .env.production.sample .env.staging.sample
现在你可以在每个文件中添加变量。
MY_ENV=This is my production envinronment variable
并将该值显示在您的 Next.js 登录页面中:
export default function Home() {
return (
<div className={styles.description}>
<div>Coming from `.env`: {process.env.MY_ENV}</div>
</div>
);
}
接下来,我们需要将示例.env文件复制到镜像中。为此,我们将通过build命令传递构建环境作为参数,因此我们需要Dockerfile相应地修改我们的代码。
第一步,我们声明一个名为 的参数SETUP_ENVINROMENT。该参数的默认值为production。
FROM node:20-alpine AS base
ARG SETUP_ENVINROMENT=production
接下来,builder我们将使用SETUP_ARGUMENT复制和重命名相应.env文件的方法。
FROM base AS builder
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy the .env{SETUP_ENVINROMENT} file to the container
# This will copy the .env.production.sample -> .env file if the SETUP_ENVINROMENT is production
COPY .env.$SETUP_ENVINROMENT.sample .env
您可以尝试为每个部署环境构建单独的镜像,并尝试更改MY_ENV变量并重新运行每个容器。
❏ ~ echo Building staging env
❏ ~ docker build --build-arg SETUP_ENVINROMENT=staging --tag my-app-docker-staging .
创建 CI/CD 流水线
创建和部署 Docker 镜像的过程称为流水线。流水线是一系列按顺序执行的步骤,用于构建和部署应用程序。您可以使用 CI/CD 工具来自动化此过程,并简化部署管理。
有很多 CI/CD 工具可用于自动化 Docker 部署,例如 Jenkins、CircleCI 和 Travis CI。在本教程中,我们将使用 GitHub Actions 来自动化 Docker 部署。
创建发布工作流程
GitHub Actions 是一款 CI/CD 工具,可帮助您自动化软件开发工作流程。它提供了一系列预定义的工作流程,可用于简化部署过程,而且对于业余项目和开源项目来说几乎是免费的。
为了给我们的项目设计自定义工作流程,我们需要在代码仓库中创建一个 YAML 文件。该文件包含了构建和部署项目所需的步骤。
为了将我们的镜像发布到 Docker Hub 上作为公开访问的镜像,我们需要在 Docker Hub 中生成一个令牌。您可以通过访问您的 Docker Hub 帐户并点击相应链接来创建令牌Account Settings -> Security -> New Access Token。您可以为令牌添加标签并授予其相应read的write权限。
接下来,将令牌作为 GitHub Actions 可访问的密钥添加到您的 GitHub 仓库中。您可以通过访问您的仓库并前往 `<repository_name>` 来完成此操作Settings -> Secrets and Variables -> Actions。添加两个新的仓库密钥,分别命名为 `<repository_name>`DOCKER_USER和DOCKERHUB_TOKEN`<repository_secret>`,并将它们的值分别设置为您的 Docker 用户名和您刚刚创建的令牌。
完成后,您可以前往您的 GitHub 仓库,并在该.github/workflows目录中创建一个新文件。您可以为该文件命名release.yml,并插入以下内容:
name: Build and Publish Docker image
on:
push:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3.5.3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.1
- name: Login to DockerHub
uses: docker/login-action@v2.2.0
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v3
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ vars.DOCKER_USER }}/my-nextjs-docker
readme-filepath: ./README.md
- name: Build and push
uses: docker/build-push-action@v4.1.1
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ vars.DOCKER_USER }}/my-nextjs-docker:latest
platforms: linux/amd64,linux/arm64/v8
该配置乍看之下可能有些复杂,但让我们将其分解成更小的组成部分。
首先,我们需要定义工作流的触发动作。在本例中,我们希望在每次代码推送到main分支时启动工作流。接下来,我们需要定义要执行的作业。
我们还有一个名为 `buildx` 的单任务docker,它将在最新版本的 Ubuntu 上运行。该任务包含一个步骤:检出代码,然后安装 Docker Buildx 插件。Buildx 是一个 Docker CLI 插件,它扩展了 docker 命令的功能,使其能够完全支持 Moby BuildKit 构建工具包提供的所有功能。
接下来,我们需要使用该操作登录 Docker Hub docker/login-action。然后,我们需要使用该操作设置 Docker 镜像的描述peter-evans/dockerhub-description。最后,我们需要使用该docker/build-push-action操作构建并推送镜像,这样就基本完成了。
总结
在本教程中,我们探讨了如何使用 Docker 将 Next.js 应用程序容器化。我们还深入研究了如何使用 GitHub Actions 实现 Docker 部署自动化。我们希望本教程能为 Docker 和 Next.js 提供一个有用的入门指南。如果您有任何问题或建议,请随时与我们联系。
此外,您还可以查看本教程的完整示例。