Network issues with Nextjs (T3 App) and Docker on production environment [TypeError: fetch failed at Object.fetch]

717 Views Asked by At

I have a Nextjs project using the create T3 app stacks (with the exception of using drizzle orm instead pf Prisma). On my development environment everything is working just fine Now I wanted to deploy my project on my linux server using docker. Ubuntu 22.04.2 LTS - Platform: linux/arm64/v8

The docker-compose build process is working just fine. Both services are up and running and the environments variables are set up correctly (unless I forgot to set and important one?) but I cannot acces any pages of the app. When I try to open it the server is logging several runtime erros complaining about a refused connection to localhost. My setup and the error are posted below.

My Dockerfile (from T3 docs)

##### DEPENDENCIES

FROM --platform=linux/arm64/v8 node:16-alpine3.17 AS deps
RUN apk add --no-cache libc6-compat openssl1.1-compat
WORKDIR /app

# Install dependencies based on the preferred package manager

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml\* ./

RUN \
    if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
    elif [ -f package-lock.json ]; then npm ci; \
    elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
    else echo "Lockfile not found." && exit 1; \
    fi

##### BUILDER

FROM --platform=linux/arm64/v8 node:16-alpine3.17 AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# ENV NEXT_TELEMETRY_DISABLED 1

RUN \
    if [ -f yarn.lock ]; then SKIP_ENV_VALIDATION=0 yarn build; \
    elif [ -f package-lock.json ]; then SKIP_ENV_VALIDATION=0 npm run build; \
    elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && SKIP_ENV_VALIDATION=0 pnpm run build; \
    else echo "Lockfile not found." && exit 1; \
    fi

##### RUNNER

FROM --platform=linux/arm64/v8 node:16-alpine3.17 AS runner
WORKDIR /app

ENV NODE_ENV production

# ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/next.config.mjs ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# USER nextjs
EXPOSE 3000
ENV PORT 3000

CMD ["node", "server.js"]

My docker-compose

version: '3.8'
services:
  nextjs-app-bpl-v3:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: nextjs-app-bpl-v3
    restart: unless-stopped
    working_dir: /app
    ports:
      - '3187:3000'
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres-db-bpl-v3:5432/${POSTGRES_DB}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - NEXTAUTH_URL=${NEXTAUTH_URL}
      - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
      - DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET}
    image: t3-app-bpl-v3
    depends_on:
      - postgres-db-bpl-v3
    networks:
      - nginx_network

  postgres-db-bpl-v3:
    image: postgres:latest
    container_name: postgres-db-bpl-v3
    restart: unless-stopped
    ports:
      - '5578:5432'
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    volumes:
      - postgres-data-bpl-v3:/var/lib/postgresql/data
    networks:
      - nginx_network

volumes:
  postgres-data-bpl-v3:


networks:
  nginx_network:
    external: true

My environment (.env)

NEXTAUTH_SECRET="MY_SECRET"
NEXTAUTH_URL="https://example.com"
# Next Auth Providers
DISCORD_CLIENT_ID="DC_ID"
DISCORD_CLIENT_SECRET="DC_SECRET"

POSTGRES_USER="username"
POSTGRES_PASSWORD="my_password"
POSTGRES_DB="db_name"

next.config.mjs

await import("./src/env.mjs");

/** @type {import("next").NextConfig} */
const config = {
  reactStrictMode: true,
  i18n: {
    locales: ["en"],
    defaultLocale: "en",
  },
  webpackDevMiddleware: (
    /** @type {{ watchOptions: { poll: number; aggregateTimeout: number; }; }} */ config
  ) => {
    config.watchOptions = {
      poll: 1000,
      aggregateTimeout: 300,
    };
    return config;
  },
  swcMinify: true,
  output: "standalone",
};

export default config;

Container logs:

Attaching to nextjs-app-bpl-v3
nextjs-app-bpl-v3     | - ready started server on 07135160c06c:3000, url: http://07135160c06c:3000
nextjs-app-bpl-v3     | Listening on port 3000 url: http://07135160c06c:3000
nextjs-app-bpl-v3     | Warning - Configuring `keepAlive: false` is deprecated. Use `{ headers: { connection: "close" } }` instead.
nextjs-app-bpl-v3     | - error Failed to handle request for /
nextjs-app-bpl-v3     | TypeError: fetch failed
nextjs-app-bpl-v3     |     at Object.fetch (/app/node_modules/next/dist/compiled/undici/index.js:1:26669)
nextjs-app-bpl-v3     |     at processTicksAndRejections (node:internal/process/task_queues:96:5)
nextjs-app-bpl-v3     |     at async invokeRequest (/app/node_modules/next/dist/server/lib/server-ipc/invoke-request.js:21:12)
nextjs-app-bpl-v3     |     at async requestHandler (/app/node_modules/next/dist/server/lib/start-server.js:336:33)
nextjs-app-bpl-v3     |     at async Server.<anonymous> (/app/node_modules/next/dist/server/lib/start-server.js:152:13) {
nextjs-app-bpl-v3     |   cause: Error: connect ECONNREFUSED 127.0.0.1:45761
nextjs-app-bpl-v3     |       at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1278:16) {
nextjs-app-bpl-v3     |     errno: -111,
nextjs-app-bpl-v3     |     code: 'ECONNREFUSED',
nextjs-app-bpl-v3     |     syscall: 'connect',
nextjs-app-bpl-v3     |     address: '127.0.0.1',
nextjs-app-bpl-v3     |     port: 45761
nextjs-app-bpl-v3     |   }
nextjs-app-bpl-v3     | }
nextjs-app-bpl-v3     | - error Failed to handle request for /favicon.ico
nextjs-app-bpl-v3     | TypeError: fetch failed
nextjs-app-bpl-v3     |     at Object.fetch (/app/node_modules/next/dist/compiled/undici/index.js:1:26669)
nextjs-app-bpl-v3     |     at processTicksAndRejections (node:internal/process/task_queues:96:5)
nextjs-app-bpl-v3     |     at async invokeRequest (/app/node_modules/next/dist/server/lib/server-ipc/invoke-request.js:21:12)
nextjs-app-bpl-v3     |     at async requestHandler (/app/node_modules/next/dist/server/lib/start-server.js:336:33)
nextjs-app-bpl-v3     |     at async Server.<anonymous> (/app/node_modules/next/dist/server/lib/start-server.js:152:13) {
nextjs-app-bpl-v3     |   cause: Error: connect ECONNREFUSED 127.0.0.1:45761
nextjs-app-bpl-v3     |       at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1278:16) {
nextjs-app-bpl-v3     |     errno: -111,
nextjs-app-bpl-v3     |     code: 'ECONNREFUSED',
nextjs-app-bpl-v3     |     syscall: 'connect',
nextjs-app-bpl-v3     |     address: '127.0.0.1',
nextjs-app-bpl-v3     |     port: 45761
nextjs-app-bpl-v3     |   }
nextjs-app-bpl-v3     | }

Why is the runtime trying to connect to localhost? I don't see any point where I have a reference to this. The error is happening on the server (not on the client when opening the page) The client only shows a 500: internal server error.

My workaround for now is: I removed the output: "standalone" and updated the Dockerfile by removing the RUNNER section and start the server with npm run start (next start) This is working although I am still wondering why it does not work with the standalone output

My question and goal would be to use the standalone server as intended by fixing this TypeError: fetch failed. What are the exact downside of not using the standalone build inside of my docker container? Thanks for any explanation!

1

There are 1 best solutions below

0
surveyed_monkey On

I may be mistaken, but I think some ENV variables are required at build time and some at run time when using the output: standalone option in your next.config.{m}js file. I'm not sure if this is correct or not, but this is how I have my Dockerfile working on next 13.4.12:

FROM base AS builder

ARG DATABASE_URL
ARG VERCEL_URL
ARG NEXT_PUBLIC_CLIENTVAR

ENV DATABASE_URL=${DATABASE_URL}
ENV VERCEL_URL=${VERCEL_URL}
ENV NEXT_PUBLIC_CLIENTVAR=${NEXT_PUBLIC_CLIENTVAR}
...
FROM base AS runner

ARG DATABASE_URL
ARG VERCEL_URL
ARG NEXT_PUBLIC_CLIENTVAR

ENV DATABASE_URL=${DATABASE_URL}
ENV VERCEL_URL=${VERCEL_URL}
ENV NEXT_PUBLIC_CLIENTVAR=${NEXT_PUBLIC_CLIENTVAR}

and in my docker-compose.yml:

services:
  next-app:
    restart: unless-stopped
    build:
      context: ./
      dockerfile: Dockerfile
      args:
        NEXT_PUBLIC_CLIENTVAR: ${NEXT_PUBLIC_CLIENTVAR}
    environment:
      - DATABASE_URL=${DATABASE_URL}
    ports:
      - 3000:3000

Also worth noting that there is currently an open issue with next 13.4.13 and above with ENV variables and output: standalone.