Search code examples
node.jsreactjsdockerexpressmern

When to replace localhost with service name in docker?


I have reactjs app and express app dockerized.
This is my docker-compose.yml file:

version: "3"

services:
  client:
    image: react-app
    build: ./client
    ports:
      - "3000:3000"
    volumes:
      - ./client/:/usr/src/app
      - /usr/src/app/node_modules
    container_name: mern-app-client
    environment:
      CHOKIDAR_USEPOLLING: "true"
  server:
    image: express-app
    build: ./server
    ports:
      - "5000:5000"
    volumes:
      - ./server/:/usr/src/app
      - /usr/src/app/node_modules
    container_name: mern-app-server
    environment:
      CHOKIDAR_USEPOLLING: "true"

I have 2 dockerfiles, 1 in each client and server (built from node images). After running docker compose up, I have server running at http://localhost:5000 and react app at http://localhost:3000.

Everything is working fine when I have base_url as http://localhost:5000 in react app's axios/fetch. In some blogs, not necessarily of mern, I saw localhost being replaced with service name. I tried the same and replaced the base_url in react app with http://server:5000 but it didn't work.

Now I am confused in the core concept of when should we replace the base_url and why is it needed? I would be very grateful if somebody could explain. Thanks for your time.


Solution

  • When you go to your browser and type http://localhost:3000 in, the browser makes a call to the client container, does an HTTP GET to retrieve the Javascript code, and the browser actually runs the code. This is a critical difference: any fetch calls or similar from your React application are running in the end user's browser, not inside Docker.

    If a call is coming from the browser; it is part of your front-end application; or it is otherwise coming from outside Docker, then you need to use the host system's DNS name and the first published ports: number. If the browser and the containers are both running on the same system you can use localhost; if Docker is running in a VM (perhaps on the older Docker Toolbox setup) you need the VM's IP address.

    +-------------+                         +-----------------------+
    | Browser     |  http://localhost:3000/ | client:               |
    |   React app | ----------------------> |   ports: ['3000:...'] |
    +-------------+                         +-----------------------+
    

    It's also possible for one container to directly call each other. Maybe the client container makes a call to server as part of prerendering a page before it gets sent back to the browser; maybe server has a backing db. In this case you can use the Compose service name as a host name, and whatever port the server process is listening on. ports: aren't required and are ignored if present.

    +----------+                      +-------------------------+
    | server:  |  postgres://db:5432/ | db:                     |
    |          | -------------------> |   # ports: ['...:5432'] |
    +----------+                      +-------------------------+
    

    This setup is described further in Networking in Compose in the Docker documentation. You do not generally need to explicitly specify container_name: or networks:; Compose provides reasonable working defaults for these things for you.

    For a browser-based application that needs to be configured with URLs for both the application itself and also a separate API server, a good practice is to set up a reverse proxy as a container. This could be something like Nginx, or in a pure development environment Webpack's proxy server. Since the proxy makes calls between two containers, you'd configure it with the Docker-internal URL http://server:5000 for the backend. This means that both the in-browser code and the backend APIs are on the same host and port, though, and so you can use a path-only relative URL /api/..., bypassing the question of which host and port to use.