Nextjs 多阶段 docker 构建因服务器组件和服务器操作而失败

问题描述 投票:0回答:1

Next.js 14 和 Nest.js API Docker 化

我构建了 Next.js 14 应用程序,我的 API 是使用 Nest.js 和 MongoDB 编写的。对于本地测试,我正在尝试对 API 和前端进行 dockerize。

Docker 化 API 访问

我的带有 MongoDB 的 Nest.js 已成功 Docker 化,我可以使用以下方式访问 API:

容器正在使用以下名称运行:

  • mongo-db
  • 巢服务器

nest-server
正在使用以下 URL 访问
mongo-db
DATABASE_URI='mongodb://用户 @mongo-db:27017/nesttodos?authSource=admin'

在这里,我正在使用正在运行的 MongoDB 容器访问 MongoDB。

Next.js 环境变量

当我使用以下 .env 变量构建 Next.js 应用程序时:

NEXT_PUBLIC_BASE_FRONTEND_URL=http://nextjs-app:4000
NEXT_FRONTEND_URL=http://nextjs-app:4000
NEXT_BASE_BACKEND_URL=http://nest-server:3001
NEXT_PUBLIC_BASE_BACKEND_URL=http://nest-server:3001
NEXT_PUBLIC_ENV_MODE=production
ENV_MODE=production

我在构建时遇到错误,因为它在生成静态页面时进行 API 调用。下面,我提到了我遇到的错误,以及我的 Dockerfile 和 Docker Compose 文件的源代码。

这是我的 docker-compose.yml 文件:

version: "3.9"
services:
  nextjs-app:
    build:
      context: ..
      dockerfile: docker/Dockerfile.next.prod
    container_name: nextjs-app
    env_file:
      ../nextjs/.env
    ports:
      "4000:4000"
    restart: unless-stopped
    networks:
      hq-network:

networks:
  hq-network:
    driver: bridge

我的docker/Dockerfile.next.prod文件如下:

# Stage 1: Build environment (with dependencies)
FROM node:22-alpine as base

# 1. Install dependencies
FROM base as deps

# 2. libc6-compat is needed
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies
COPY ../nextjs/package*.json ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm install; \
  else echo "Lockfile not found." && exit 1; \
  fi

# 3. Rebuild source code
FROM base as builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY ../nextjs ./
COPY ../nextjs/.env ./
RUN npm run build

# 4. Production image, copy all the files and running Next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production

# Now copy files from builder
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 4000
ENV PORT=4000
CMD ["node", "server.js"]

我的page.tsx文件包含以下代码:

import { RscCreateTodo, RscReadAllTodo } from "@/actions/RscActions";
import { todoData } from "@/types/allTypes";

export default async function Home() {
  const AllTodos = await RscReadAllTodo();

  // Create todo
  const addTodo = async (event: FormData) => {
    "use server";
    const todoValue = event.get("todo")?.toString();
    if (!todoValue) {
      return;
    }
    await RscCreateTodo({
      todo: todoValue,
    });
    event.set("todo", "");
  };

  return (
    <div className="flex min-h-screen items-start justify-items-center gap-16 p-8 pb-20 sm:p-20">
      <main className="w-full">
        <div className="my-10 w-full">
          <h1 className="mb-4 text-center text-3xl font-semibold">
            To Do List
          </h1>
          <div className="mx-auto md:w-1/2">
            <div className="rounded-lg bg-white p-6 shadow-md">
              <form id="todo-form" action={addTodo}>
                <div className="mb-4 flex">
                  <input
                    name="todo"
                    type="text"
                    className="mr-2 w-full rounded-lg border border-blue-300 px-4 py-2 focus:border-blue-500 focus:outline-none"
                    id="todo-input"
                    placeholder="Add new task"
                    required
                  />
                  <button className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700">
                    Add
                  </button>
                </div>
              </form>
              <ul
                id="todo-list"
                className="scrollbarwork max-h-[calc(100vh-30vh)] overflow-x-hidden"
              >
                {/* todos list here */}
                {AllTodos?.map((todo: todoData, index: number) => {
                  return (
                    <li key={todo?._id}>
                      {index + 1}. {todo.todo}
                    </li>
                  );
                })}
              </ul>
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}

我在 Docker 构建过程中遇到以下错误:

CACHED [nextjs-app builder 2/5] COPY --from=deps /app/node_modules ./node_modules  
[nextjs-app builder 3/5] COPY ../nextjs ./  
[nextjs-app builder 4/5] COPY ../nextjs/.env ./  
ERROR [nextjs-app builder 5/5] RUN npm run build

> [nextjs-app builder 5/5] RUN npm run build:
> 2.544
> 2.544 > [email protected] build
> 2.544 > next build
> 2.544
> 5.089 Attention: Next.js now collects completely anonymous telemetry regarding usage.
> 5.091 This information is used to shape Next.js' roadmap and prioritize features.
> 5.091 You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting
> 5.091 https://nextjs.org/telemetry
> 5.091
> 5.370   ▲ Next.js 14.2.13
> 5.370   - Environments: .env
> 5.370   - Experiments (use with caution):
> 5.370     · scrollRestoration
> 5.373
> 5.630    Creating an optimized production build ...
> 36.05  ✓ Compiled successfully
> 36.05    Linting and checking validity of types ...
> 43.03    Collecting page data ...
> 45.37    Generating static pages (0/5) ...
> 45.85    Generating static pages (1/5)
> 45.85    Generating static pages (2/5)
> 45.85    Generating static pages (3/5)
> 48.98 TypeError: fetch failed
> 48.98     at node:internal/deps/undici/undici:13185:13
> 48.98     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
> 48.98     at async o (/app/.next/server/app/page.js:2:63081)
> 48.98     at async o (/app/.next/server/app/page.js:2:64069) {
> 48.98   digest: '3194799139',
> 48.98   [cause]: Error: getaddrinfo ENOTFOUND nest-server
> 48.98       at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26)
> 48.98       at GetAddrInfoReqWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
> 48.98     errno: -3008,
> 48.98     code: 'ENOTFOUND',
> 48.98     syscall: 'getaddrinfo',
> 48.98     hostname: 'nest-server'
> 48.98   }
> 48.98 }
> 52.24 TypeError: fetch failed
> 52.24     at node:internal/deps/undici/undici:13185:13
> 52.24     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
> 52.24     at async o (/app/.next/server/app/page.js:2:63081)
> 52.24     at async o (/app/.next/server/app/page.js:2:64069) {
> 52.24   digest: '3194799139',
> 52.24   [cause]: Error: getaddrinfo ENOTFOUND nest-server
> 52.24       at GetAddrInfoReqWrap.on

nestjs docker-build nextjs14 getaddrinfo multistage
1个回答
0
投票

您遇到的错误与 Docker 设置中的 DNS 问题有关。本质上,当您的 Next.js 应用程序在构建阶段尝试连接到

nest-server
时,它无法找到
nest-server
,因为此时 Docker 网络未处于活动状态。

以下是发生的情况以及解决方法:

为什么会发生这种情况:

  1. 构建阶段与运行时:在 Next.js 应用程序的构建阶段,
    nest-server
    API 尚未启动,因此应用程序无法访问它。构建过程发生在 Docker 容器完全运行之前,因此此时无法访问网络(包括
    nest-server
    )。
  2. 静态站点生成 (SSG):假设
    nest-server
    API 可用,Next.js 尝试在构建过程中获取数据。但由于服务器尚未运行,因此无法连接。
  3. DNS 解析:只有在容器启动并运行后,
    nest-server
    名称才会被识别。但是,在构建过程中,Docker 无法解析该名称。

如何解决:

1. 将数据获取推迟到运行时

不要在构建过程中尝试获取数据,而是等到应用程序运行。您可以在应用程序启动后使用

getServerSideProps
获取数据。这可确保发出数据请求时 API 可用。

2. 在构建期间模拟 API:

如果在构建阶段无法访问 API,您可以提供一些模拟数据以保持构建过程顺利进行。一旦应用程序运行,它将使用真正的API。

3. 使用 Docker 构建参数:

您可以在构建阶段设置不同的 API URL(例如,模拟 API 或不同的配置)。这可以让您避免在构建过程中连接到真实的

nest-server

4. 在构建期间跳过 API 调用

确保在构建应用程序时跳过 API 调用。您可以编写一个条件来检查应用程序是否在构建环境中运行,并避免在应用程序上线之前获取数据。

5. 在开发中使用本地主机

对于本地开发,您可以改用

localhost
而不是像
nest-server
这样的 Docker 服务名称。这有助于避免本地测试时出现 DNS 问题。

结论:

最简单的解决方案是通过在 Next.js 中使用

getServerSideProps
将数据获取延迟到运行时。这可确保您的应用程序等到所有服务(包括
nest-server
)都运行后再尝试获取数据。

© www.soinside.com 2019 - 2024. All rights reserved.