Solucionar el error “fetch failed” de NextJs y Supabase con Docker

Aprende a solucionar el error “fetch failed” de NextJs y Supabase con Docker, un error que aparece en el entorno local con estas tecnologías.

Ya llevo un tiempo explorando el uso de Supabase en combinación con NextJs, para el desarrollo de aplicaciones “fullstack”.

En líneas generales, estas dos tecnologías hacen un tándem genial.

Con muy poco esfuerzo permiten a cualquier desarrollador crear aplicaciones web completas.

Por un lado Supabase actúa de “backend as a service”, solventando mucha complejidad en la preparación e interacción con bases de datos y sistemas de autenticación.

Por el otro, NextJs permite crear aplicaciones web full-stack con todas las ventajas propias de React.

Sin embargo, al combinar estas tecnologías con Docker, puede aparecer el siguiente error:

Se trata de una situación muy peculiar, ya que solo se produce si NextJs trata de comunicarse con Supabase desde el backend, y si ambos están dentro de contenedores Docker.

Contexto NextJs + Supabase en contenedores docker
Contexto NextJs + Supabase en contenedores docker

Sé que es un poco enrevesado, pero no te preocupes, más adelante te explico detalladamente el contexto y cómo se resuelve.

Tras entender y resolver el caso, me ha parecido lo suficientemente interesante, como para escribir sobre ello.

Espero que si te has encontrado con esta situación, este post te ayude a resolver el problema.

Antes de entrar en detalle, quiero avisar de que, aunque voy a hacer un breve resumen de estas tecnologías, es necesario estar familiarizado con ellas para entender algunos de los conceptos que voy a tratar.

Nextjs, Supabase y Docker las piezas del puzzle

Todo empezó al levantar un proyecto completamente nuevo de NextJs.

NextJs es un framework de desarrollo de aplicaciones web, que integra y extiende React. 

Una de sus principales características es que permite programar código JSX tanto frontend, como backend gracias a la implementación de componentes en el lado del servidor o “server side components”.

Si estás un poco familiarizado con NextJs y React, sabrás que los componentes definidos como “use client” són interpretados y ejecutados por el navegador.

Mientras que en el lado del servidor, es NodeJs quien se encarga de la ejecución.

A pesar de que esa, es una descripción un tanto superficial de NextJs, me permite contextualizar una de las primeras causas del problema.

En mi máquina tengo instalada una determinada versión de Nodejs, mientras que la versión más reciente de NextJs requiere al menos de Node 18.17 para funcionar correctamente.

Por otras circunstancias, actualizar la versión de NodeJs en mi ordenador no era posible, e instalar paquetes como “nvm” no me parecía una buena idea.

Así que opté por dockerizar la aplicación y levantarla en un contenedor de Docker.

FROM node:alpine3.18
WORKDIR /app
COPY package.json .
RUN npm install
COPY . /app
EXPOSE 3000
CMD ["npm", "run", "dev"]

Hasta ahí todo funcionó correctamente, NextJs corría dentro de un contenedor especialmente habilitado para ello.

Por otro lado, también levanté una instancia de Supabase en local.

Supabase es un BaaS (backend as a service) open source que ofrece la posibilidad, entre otras cosas, de manejar bases de datos y sistemas de autenticación cómodamente.

Por aquí ya vimos otro BaaS de Google llamado Firebase, te dejo el enlace por si deseas conocer más.

Crear un proyecto fullstack con Firebase

Supabase permite crear proyectos directamente en su servicio cloud, con costes muy económicos.

Pero además, ofrece la posibilidad de levantar una instancia en tu máquina mediante CLI y Docker.

https://supabase.com/docs/guides/cli/local-development

Siguiendo el proceso descrito en el enlace a la documentación, en unos pocos minutos tenía un proyecto Supabase listo y funcionando en mi ordenador.

Problemas al conectar NextJs 14 con  Supabase en un entorno dockerizado

Todo estaba listo para conectar Nextjs con Supabase. Cada uno dentro de su contenedor Docker.

Así que instalé una dependencia para facilitar la comunicación entre Nextjs y Supabase.

npm i @supabase/auth-helpers-nextjs @supabase/supabase-js

Esta librería agrega un nivel de abstracción que simplifica las comunicaciones entre ambos recursos.

Lo único que requiere, es añadir unas variable de entorno con los nombres que siguen:

NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbxxxxx…..

Aquí es clave entender que localhost:54321 es visible desde la máquina “host” a través del “port forwarding” aplicado al levantar el contenedor.

Internamente la librería AuthHelperNextJs usa estas variables como “endpoint” y como clave para validar la comunicación.

Tras incluirlas, programé un sencillo sistema de login, y una consulta a la base de datos, para verificar que todo iba bien.

En este punto es donde apareció en la consola del servicio NextJs, el críptico mensaje de error:

{
  message: 'TypeError: fetch failed',
  details: 'TypeError: fetch failed\n' +
	'at node:internal/deps/undici/undici:12443:11\n' +
	'at process.processTicksAndRejections (node:internal/process/task_queues:95:5)',
  hint: '',
  code: ''
}

Lo curioso del caso es que, el error solo aparecía con peticiones que se realizaban desde el backend de NextJs.

Todas las comunicaciones que Nextjs iniciaba en el navegador hacia Supabase, funcionaban correctamente.

Contexto localhost dentro y fuera de contenedores Docker

Tengo que reconocer que, a pesar de que ahora me parece evidente, en su momento me costó entender qué estaba fallando.

¿Has logrado anticipar el origen del problema?

Exactamente, NextJs tiene configurado la ruta “localhost:54321” como endpoint para conectarse con la instancia de Supabase.

Pero al encontrarse dentro de un contenedor, ese “localhost” provoca que se esté apuntando a sí mismo, y por supuesto, allí no existe ningún servicio corriendo en el puerto 54321.

Por contra, cuando la petición se realiza desde el navegador, por ejemplo con la API Fetch, el origen es la máquina “host”, es decir tu ordenador, donde tienes los contenedores levantados.

Por consiguiente, gracias al “Port forwarding”, la conexión con Supabase se puede establecer a través de la ruta “localhost:54321”.

Error "fetch failed" al llamar desde dentro del contenedor docker
Error «fetch failed» al llamar desde dentro del contenedor docker

Resolviendo la comunicación entre contenedores Docker

Habiendo identificado el problema, enumeré posibles soluciones, y de nuevo me fui encontrando con sorpresas.

La primera idea fue modificar la variable de entorno, para que tuviera un dominio distinto a localhost.

Docker permite utilizar el dominio host.docker.internal.

https://docs.docker.com/desktop/networking/#use-cases-and-workarounds

A través de este dominio se puede mapear una petición desde un contenedor hacía la máquina” host”.

Edité la variable de entorno:

NEXT_PUBLIC_SUPABASE_URL=http://host.docker.internal:54321

Este cambio provocó que el error desapareciera. Pero la alegría duró poco, ahora el problema se daba en el frontend.

Editar endpoint para que apunte a host.docker.internal:54321
Editar endpoint para que apunte a host.docker.internal:54321

Y tiene todo el sentido si lo piensas. En este punto, las peticiones que se inician en el navegador, (y por consiguiente en la máquina “host”) desconocen a qué IP hace referencia el dominio host.docker.internal.

Así que se me ocurrió un último ajuste para lidiar con esta situación.

Editar el archivo /etc/hosts del sistema, para indicar a mi ordenador que el dominio debe apuntar de nuevo a la IP local.

127.0.0.1   host.docker.internal
Solucionar el error “fetch failed” de NextJs y Supabase con Docker
Solucionar el error “fetch failed” de NextJs y Supabase con Docker

Con esto, todo funcionó a las mil maravillas, NextJs lograba encontrar Supabase tanto desde el frontend, como desde el backend, y todo en local a través de contenerdores Docker. Y yo pude dormir feliz.

Conclusiones y recursos adicionales

Espero que esta publicación te haya ayudado a responder tus dudas.

Si alguna parte no se acaba de entender, o se te ha ocurrido una mejor forma de solventar el problema expuesto, estaré encantado de que me escribas.

Dejo un listado de enlaces a recursos adicionales para seguir aprendiendo sobre estas tecnologías

¡Hasta la próxima!

Deja un comentario