React/Next.js docker build failing when trying to reach out to local API endpoint
Asked Answered
I

5

7

I cannot seem to get my node:alpine Docker image for a react/next.js to build.

I can get it to build fine locally, but I never see the traffic in my API logs when the Docker build reaches out to the API endpoint, which is itself running in a Docker container (nginx, headless craft cms, etc.).

The build does not seem to like localhost at all, as I've tried:

http://localhost:9000/api

...and this is the message I get in the logs:

#15 13.76 > Build error occurred
#15 13.76 FetchError: request to http://localhost:9000/api failed, reason: connect ECONNREFUSED 127.0.0.1:9000

I've heard that I can use the hostname in place of "localhost", so in my MacOS terminal I've typed "hostname" and I've swapped out the localhost for this value. This doesn't outright error out, but the build hangs on the "Generating pages..." step of the build.

How can I get the Docker build to recognize localhost, or put another way, how can I set my API URL to an endpoint hosted by a local Docker container?

Here is my Dockerfile:

# Install dependencies only when needed
FROM node:alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile

# Rebuild the source code only when needed
FROM node:alpine AS builder

WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN yarn static

# Production image, copy all the files and run next
FROM node:alpine AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

USER nextjs

EXPOSE 3000

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry.
# ENV NEXT_TELEMETRY_DISABLED 1

CMD ["yarn", "start"]
Ikon answered 30/4, 2021 at 0:52 Comment(0)
C
4

Thanks for your post - it helped me get to a solution that should do the trick. I found that defining the bridge network gateway is a more elegant solution; it allows you to define the PUBLIC_NEXT_API_URL and never change it.

Here's how you can do it in a docker-compose file:

services:
  db:
    image: postgres
    restart: always
    networks:
      - stagingnetwork

  api:
    image: express
    restart: always
    ports:
      - '3008:3008'
    depends_on:
      - db
    networks:
      - stagingnetwork


  adminer:
    image: adminer
    restart: always
    networks:
      - stagingnetwork
    ports:
      - 8080:8080

networks:
  stagingnetwork:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
          gateway: 172.28.0.1

Now you can set PUBLIC_NEXT_API_URL to http://172.28.0.1:3008/api, and be confident that this will be the endpoint every time you fire up the docker-compose.

Also, note that you may need to remove an existing network and then "docker-compose up --force-recreate" in order to get the IP correct.

Chuff answered 16/7, 2021 at 16:26 Comment(1)
Great to know! Thank you for posting this!Ikon
I
5

Okay, I figured out the solution, or a solution rather.

After digging around, I found that I needed to connect to the "bridge network".

I found out what the bridge network IP was by first typing in "docker network ls":

enter image description here

This gave me the name of the network I needed to get the IP for, I did this by typing "docker network inspect craftcms_default":

enter image description here

This outputs quite a bit in my case, but the only thing I need is "Gateway" IP, which here is 172.19.0.1

My nginx server is mapped 9000:80, so the headless Craft CMS API is at:

http://172.19.0.1:9000/api

...however, as a quick sanity check I entered this into the browser and the result was an ever spinning/loading page. Undeterred, I entered this into env.local as the PUBLIC_NEXT_API_URL and the static site generation completed fine during the Docker build.

I'm still experimenting with an elegant way to set this env var within the Docker build, so if anyone has any suggestions I'd love to hear!

This is was an interesting challenge considering I couldn't find any answers/Google results specifically for a process running as a result of a Docker build being unable to make a fetch to a URL hosted within another locally running container. Hopefully this helps anyone else struggling with this issue!

Ikon answered 30/4, 2021 at 17:38 Comment(0)
C
4

Thanks for your post - it helped me get to a solution that should do the trick. I found that defining the bridge network gateway is a more elegant solution; it allows you to define the PUBLIC_NEXT_API_URL and never change it.

Here's how you can do it in a docker-compose file:

services:
  db:
    image: postgres
    restart: always
    networks:
      - stagingnetwork

  api:
    image: express
    restart: always
    ports:
      - '3008:3008'
    depends_on:
      - db
    networks:
      - stagingnetwork


  adminer:
    image: adminer
    restart: always
    networks:
      - stagingnetwork
    ports:
      - 8080:8080

networks:
  stagingnetwork:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
          gateway: 172.28.0.1

Now you can set PUBLIC_NEXT_API_URL to http://172.28.0.1:3008/api, and be confident that this will be the endpoint every time you fire up the docker-compose.

Also, note that you may need to remove an existing network and then "docker-compose up --force-recreate" in order to get the IP correct.

Chuff answered 16/7, 2021 at 16:26 Comment(1)
Great to know! Thank you for posting this!Ikon
P
3

I know this is an old issue but I'll leave my solution here for anyone new. Although the given solution did work for me I looked around for something more elegant. This problem apparently occurs because the localhost hostname refers always to the current container's localhost and not that of your host machine.

Buried in the docker for mac docs they refer to using host.docker.internal to connect from a container to another service. Using the container name as recommended in many other answers did not help me, but using host.docker.internal fixed the issue.

docs:

https://docs.docker.com/docker-for-mac/networking/

reference:

https://forums.docker.com/t/localhost-and-docker-compose-networking-issue/23100/5

Pinnatisect answered 6/8, 2021 at 10:47 Comment(0)
L
1

If you're using docker-compose, normally you could reference containers using their names, which would resolve to their IP in Docker's default network. However, it seems that this does not work during build time.

On Mac, you can connect to containers on the host through host.docker.internal as @MikejdeGroot said. So you could use http://host.docker.internal:9000/api.

On Linux, you can consider using host networking during build time, e.g.

nextjs-app:
  build:
    network: "host"
  # ...
  network_mode: "host"

With this config, you can use http://localhost:9000/api.

Lode answered 15/5, 2022 at 3:46 Comment(1)
This configuration helped me with writing e2e tests for cypress in a separate containerPinnatisect
M
0

If you're using docker copmose, your service name will be the URL endpoint that you will access: for example, if your backend's docker compose is

    build:
      context: ./backend
    ports:
      - "8000:8000"
    env_file:
      - ./backend/.env

Then, you can access the backend via

Matri answered 20/12, 2023 at 18:45 Comment(2)
Your answer seems incomplete!Chapnick
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Conquest

© 2022 - 2024 — McMap. All rights reserved.