MGLondon
MGLondon

Reputation: 1657

What is the best way to use a different API URL in production for a create-react-app using docker?

Okay so I have been banging my head around for the entire day, but still cannot figure out an elegant solution. I have created a React App which makes some API calls to a Django Server in the backend. I have created a settings.js file, which changes the API urls based on whether the app is running in development or production. The settings.js file looks like below:

let API_SERVER_VAL = '';
let MEDIA_SERVER_VAL = '';
let FRONTEND_SERVER_VAL = ''; 

switch (process.env.NODE_ENV) {
    case 'development':
        API_SERVER_VAL = 'http://localhost:8000';
        MEDIA_SERVER_VAL = 'http://localhost:8000';
        FRONTEND_SERVER_VAL = 'http://localhost:3000';
        break;
    case 'production':
        API_SERVER_VAL = process.env.API_SERVER;
        MEDIA_SERVER_VAL = process.env.API_SERVER;
        FRONTEND_SERVER_VAL = process.env.API_SERVER;
        break;
    default:
        API_SERVER_VAL = 'http://localhost:8000';
        MEDIA_SERVER_VAL = 'http://localhost:8000';
        FRONTEND_SERVER_VAL = 'http://localhost:3000';
        break;
}

export const API_SERVER = API_SERVER_VAL;
export const MEDIA_SERVER = MEDIA_SERVER_VAL;
export const FRONTEND_SERVER = FRONTEND_SERVER_VAL;

export const SESSION_DURATION = 5*3600*1000;

A sample API based on the above is shown below:

import * as settings from '../../settings';
....
axios.post(`${settings.API_SERVER}/api/auth/logout/`, ...)

The Dockerfile looks like below. I am performing a two stage build of the image.

###########
# BUILDER #
###########

# pull official base image
FROM node:12.18.3-alpine3.9 as builder

# set work directory
WORKDIR /usr/src/app

# install dependencies and avoid `node-gyp rebuild` errors
COPY package.json .
RUN apk add --no-cache --virtual .gyp \
        python \
        make \
        g++ \
    && npm install \
    && apk del .gyp

# copy our react project
COPY . .

# perform npm build
ARG API_SERVER
ENV API_SERVER=${API_SERVER}
RUN API_SERVER=${API_SERVER} \ 
  npm run build

#########
# FINAL #
#########

# pull official base image
FROM node:12.18.3-alpine3.9 

# set work directory
WORKDIR /usr/src/app

# install serve - deployment static server suggested by official create-react-app
RUN npm install -g serve

# copy our build files from our builder stage
COPY --from=builder /usr/src/app/build ./build

The docker-compose file looks like below (the react service). Basically the docker-compose react service passes an argument called API_SERVER=127.0.0.1 to the builder stage of the Dockerfile.

version: "3.7"

services:
  django:
    build:
      context: ./backend/app
      dockerfile: Dockerfile.prod
    command: gunicorn mainapp.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - django_static_volume:/usr/src/app/files_static
      - app_media_volume:/usr/src/app/files_media
    expose:
      - 8000
    # ports:
    #   - 8000:8000
    env_file:
      - ./backend/.env.prod
    depends_on:
      - db
  db:
    image: postgres:12.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./backend/.env.prod.db
  react:
    build:
      context: ./frontend/app
      dockerfile: Dockerfile.prod
      args: 
          - API_SERVER=127.0.0.1 
    volumes:
      - react_static_volume:/usr/src/app/build/static
    expose:
      - 3000
    command: serve -s build -l 3000
    depends_on:
      - django
  nginx:
    restart: always
    build: ./nginx
    volumes:
      - django_static_volume:/usr/src/app/django_files/static
      - app_media_volume:/usr/src/app/media
      - react_static_volume:/usr/src/app/react_files/static
    ports:
      - 80:80
      # - 8000:80
    depends_on:
      - react

volumes:
  postgres_data:
  django_static_volume:
  app_media_volume:
  react_static_volume:

I am also checking whether this settings.API_SERVER has been correctly passed from docker by printing it in the console on the home page. So within Home.js I have the below:

console.log(`The environment is ${process.env.NODE_ENV}`);
console.log(`The API server is ${process.env.API_SERVER}`);
console.log(`The settings API server is ${settings.API_SERVER}`);
console.log(`The PUBLIC URL is ${process.env.PUBLIC_URL}`);

However, I am seeing this in the console. enter image description here

So only the process.env.NODE_ENV is correctly being shown!! I think this is because this is showing the environment variable from the second stage of the image rather than the builder stage.

What am I doing wrong? What is the most elegant way around this with no additional npm-packages required?

Upvotes: 6

Views: 4710

Answers (1)

MGLondon
MGLondon

Reputation: 1657

I actually found the answer from the official page of create-react-app. The problem is that create-react-app would not pick up the environment variable unless it starts with a REACT_APP_ . This is what is stated here:

Note: You must create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name. Changing any environment variables will require you to restart the development server if it is running.

This is exactly the reason I was able to print out process.env.NODE_ENV in the console but not the other variables because they didn't start with REACT_APP_.

So I have changed the Dockerfile with:

# perform npm build
ARG API_SERVER
ENV REACT_APP_API_SERVER=${API_SERVER}
RUN REACT_APP_API_SERVER=${API_SERVER} \ 
  npm run build 

and, the settings.js with:

case 'production':
    API_SERVER_VAL = process.env.REACT_APP_API_SERVER;
    MEDIA_SERVER_VAL = process.env.REACT_APP_API_SERVER;
    FRONTEND_SERVER_VAL = process.env.REACT_APP_API_SERVER;
    break;

And the console now prints out whatever I am passing from the docker-compose.yaml file !!

Upvotes: 5

Related Questions