2

I'm new to web development, and I run into a strange error. I have a React/Django app which I'm trying to productionize with nginx and docker. Django runs on a postgres db, and nginx just reroutes port 80 to my react and django ports.

When I locally deploy the application using

npm run build
serve -s build

everything works as desired.

However, doing the same through Docker doesn't.

I have a Dockerfile building the react application:

FROM node:12.18.3-alpine3.9 as builder
WORKDIR /usr/src/app
COPY ./react-app/package.json .
RUN apk add --no-cache --virtual .gyp \
        python \
        make \
        g++ \
    && npm install \
    && apk del .gyp
COPY ./react-app .
RUN npm run build
FROM node:12.18.3-alpine3.9
WORKDIR /usr/src/app
RUN npm install -g serve
COPY --from=builder /usr/src/app/build ./build

Now when I use

docker-compose build
docker-compose up

I see that my Django, React, Postgres and nginx containers are all running, with nginx visible at port 80. When I open localhost in my browser, I see nginx is looking for some static react files in the right directory. However, the react files it is looking for have a different hash than the static files. The static files of both the nginx and react container are the same. So somehow, my asset-manifest.json contains the wrong filenames. Any idea what causes this is or how I can solve this?


Edit: Added docker-compose.yml:

version: "3.7"

services:
  django:
    build:
      context: ./backend
      dockerfile: Dockerfile
    volumes:
      - django_static_volume:/usr/src/app/static
    expose:
      - 8000
    env_file:
      - ./backend/.env
    command: gunicorn core.wsgi:application --bind 0.0.0.0:8000
    depends_on:
      - db
  db:
    image: postgres:12.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./postgres/.env
  react:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    volumes:
      - react_static_volume:/usr/src/app/build/static
    expose:
      - 5000
    env_file:
      - .env
    command: serve -s build
    depends_on:
      - django
  nginx:
    restart: always
    build: ./nginx
    volumes:
      - django_static_volume:/usr/src/app/django_files/static
      - react_static_volume:/usr/src/app/react_files/static
    ports:
      - 80:80
    depends_on:
      - react

volumes:
  postgres_data:
  django_static_volume:
  react_static_volume:
pplonski
  • 5,023
  • 1
  • 30
  • 34
Niels Uitterdijk
  • 713
  • 9
  • 27
  • What's in the `docker-compose.json` file? (`volumes:` in particular can instruct Docker to completely ignore changes in the built image.) – David Maze Nov 17 '20 at 11:37
  • Hi David, Thanks for your comment. I added my docker-compose.yml. As far as I understand, upon building, my frontend/Dockerfile generates the react static files and puts them into react_static_volume, which is accessible by both the nginx and react container? The asset-manifest.json is build in the same folder/operation. Therefore I am wondering how the volumes can instruct Docker to ignore changes? – Niels Uitterdijk Nov 17 '20 at 11:57
  • 1
    The volume gets populated only the very first time you run the container. After that the old contents of the volume take precedence, and changes in the image get ignored. Building the React application directly into the Nginx proxy image (maybe using a multi-stage build) will be more robust. – David Maze Nov 17 '20 at 12:49
  • Okey, I find that very strange. So inherently my Docker approach here is terrible for updating my frontend? – Niels Uitterdijk Nov 17 '20 at 14:14

1 Answers1

2

Do you need to run React in a separate container? Is there any reason for doing this? (It might be)

In my approach, I'm building React static files in nginx Dockerfile, and copy them to /usr/share/nginx/html. Then nginx serves it at location /.

nginx Dockerfile

# The first stage
# Build React static files
FROM node:13.12.0-alpine as build

WORKDIR /app/frontend
COPY ./frontend/package.json ./
COPY ./frontend/package-lock.json ./
RUN npm ci --silent
COPY ./frontend/ ./
RUN npm run build

# The second stage
# Copy React static files and start nginx
FROM nginx:stable-alpine
COPY --from=build /app/frontend/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]

nginx configuration file

server {
    listen 80;
    server_name _;
    server_tokens off;
    client_max_body_size 20M;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    location /api {
        try_files $uri @proxy_api;
    }
    location /admin {
        try_files $uri @proxy_api;
    }

    location @proxy_api {
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Url-Scheme $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass   http://backend:8000;
    }

    location /django_static/ {
        autoindex on;
        alias /app/backend/server/django_static/;
    }
}

Docker-compose

version: '2'

services:
    nginx: 
        restart: unless-stopped
        build:
            context: .
            dockerfile: ./docker/nginx/Dockerfile
        ports:
            - 80:80
        volumes:
            - static_volume:/app/backend/server/django_static
            - ./docker/nginx/development:/etc/nginx/conf.d
        depends_on: 
            - backend
    backend:
        restart: unless-stopped
        build:
            context: .
            dockerfile: ./docker/backend/Dockerfile
        volumes:
            
        entrypoint: /app/docker/backend/wsgi-entrypoint.sh
        volumes:
            - static_volume:/app/backend/server/django_static
        expose:
            - 8000        

volumes:
    static_volume: {}

Please check my article Docker-Compose for Django and React with Nginx reverse-proxy and Let's encrypt certificate for more details. There is also example of how to issue Let's encrypt certificate and renew it in docker-compose. If you will need more help, please let me know.

pplonski
  • 5,023
  • 1
  • 30
  • 34
  • Thanks for your comment, I'll give this a shot! There's no direct reason I build a separate container, I was just following an article. – Niels Uitterdijk Nov 17 '20 at 12:50
  • OK, it makes sense. Sometimes you want to serve React files with the special server because of scaling (for example). I prefer to serve it with nginx. In the case of large traffic, then I would recommend S3+CDN. – pplonski Nov 17 '20 at 13:20
  • One more thing, you can also try to serve React static files with Django, but I wouldn't recommend it. Django wasn't designed for static files serving. – pplonski Nov 17 '20 at 13:21
  • If you don't mind I got a few more questions after going through your article. - On the Nginx build, I get an error on `npm ci --silent`; 'cb() never called! error with npm itself.'. Any idea? Tried `npm install` too which doesn't work. Locally `npm ci` works fine. - Docker static volumes are never repopulated on new builds, but the static react files hosted by Nginx are repopulated? - Why do you only copy the package jsons and before installing the dependencies and not the whole react app? – Niels Uitterdijk Nov 17 '20 at 14:12
  • Please make sure that you copied correct files and that you are in the correct directory (the most common errors). I copy only `package.json` and `package-lock.json` for faster builds. When I change frontend code, I don't need to rebuild whole container. It is read from cache. I finished article today, so there might be typos. – pplonski Nov 17 '20 at 15:42
  • I got it going now. I don't get to what db your django connects though? I am now spinning up a separate container running a Postgres image. Is there a way to do this directly within the django container? – Niels Uitterdijk Nov 17 '20 at 17:02
  • I'm using the default SQLite DB. It is for simplicity. Sure, you can spin PostgreSQL in the separate container. – pplonski Nov 17 '20 at 17:40
  • I see. And now if I provide the PostgreSQL with a static volume, it's db is persistent over different builds, whereas it won't be if I leave the static volume? – Niels Uitterdijk Nov 18 '20 at 09:09