Thomas Clayson
Thomas Clayson

Reputation: 29925

Referencing Docker container from server-side (from another container) AND from client-side (browser) with same URL

I have two docker containers frontend and data-service.

frontend is using NextJS which is only relevant because NextJS has a method called getInitialProps() which can be run on the server, or can be run in the visitor's browser (I have no control over this).

In getInitialProps() I need to call an API to get the data for the page:

fetch('http://data-service:3001/user/123').then(...

When this is called on the server the API returns fine because my frontend container has access to the internal docker network and therefor can reference the data-service using the hostname http://data-service.

When this is called on the client, however, it fails (obviously) because Docker is now exposed as http://localhost and I can't reference http://data-service anymore.

How can I configure Docker so that I can use 1 URL for both use cases. I would prefer not to have to figure out which environment I'm in in my NextJS code if possible.

If seeing my docker-compose is useful I have included it below:

version: '2.2'
services:
  data-service:
    build: ./data-service
    command: npm run dev
    volumes:
      - ./data-service:/usr/src/app/
      - /usr/src/app/node_modules
    ports:
      - "3001:3001"
    environment:
      SDKKEY: "whatever"
  frontend:
    build: ./frontend
    command: npm run dev
    volumes:
      - ./frontend:/usr/src/app/
      - /usr/src/app/node_modules
    environment:
      API_PORT: "3000"
      API_HOST: "http://catalog-service"
    ports:
      - "3000:3000"

Upvotes: 9

Views: 2637

Answers (2)

Carlos Bensant
Carlos Bensant

Reputation: 287

As the accepted answer, I would've recommended you to use Runtime Configurations but I'm afraid it's been deprecated. Nevertheless, it's still possible to get the server runtime configurations "serverRuntimeConfig" value:

  1. In next.config.js
const nextConfig = {
  serverRuntimeConfig: {
    // Will only be available on the server side
    apiUrl: process.env.NEXT_SERVER_API_URL || 'http://dockerbackend:8000'
  },
  publicRuntimeConfig: {
    // Will be available on both server and client
    apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
  }
}
  1. In @/config/index.ts
import getConfig from 'next/config';

const config = getConfig();

let apiUrl = process.env.NEXT_PUBLIC_API_URL;
if (config) {
  apiUrl = config?.serverRuntimeConfig?.apiUrl
}

export {
  apiUrl
}
  1. Import the config file wherever you need the API URL:
import { apiUrl } from '@/config'

Upvotes: 0

struensee
struensee

Reputation: 606

The most elegant solution I've found is described in this post: Docker-compose make 2 microservices (frontend+backend) communicate to each other with http requests

Example implementation:

In next.config.js:

module.exports = {
  serverRuntimeConfig: {
    // Will only be available on the server side
    URI: 'your-docker-uri:port'
  },
  publicRuntimeConfig: {
    // Will be available on both server and client
    URI: 'http://localhost:port'
  }
}

In pages/index.js:

import getConfig from 'next/config';
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const API_URI = serverRuntimeConfig.apiUrl || publicRuntimeConfig.apiUrl;

const Index = ({ json }) => <div>Index</div>;

Index.getInitialProps = async () => {
       ...
       const res = await fetch(`${API_URI}/endpoint`);
       ...
}

Upvotes: 9

Related Questions