Search code examples
reactjsdockerpm2hot-reload

Automatic Reloading of React App in Browser While Using Docker and PM2


I've been trying to achieve automatic reloading of my React application in the browser on http://localhost:3000 when I make changes to the src/App.js file. I'm running my application inside a Docker container using docker-compose and managing it with PM2.

The following code makes React app run in browser successfully, but no changes are shown when I reload a page. It only works after rerunning docker-compose up. Could you please suggest a solution or identify any missing configurations?

Project Structure:

project_root/
├── app/                  # Django app backend code
│   ├── app/
│   │   ├── settings.py        
│   └── manage.py         
├── frontend/             # React app frontend code
│   ├── node_modules/     
│   ├── public/            
│   ├── src/                 
│   │   ├── App.js
│   │   └── ...
│   ├── templates/            
│   ├── ecosystem.conf.js
│   ├── package-lock.json
│   ├── package.json          
│   ├── Dockerfile.frontend   
│   └── ...
├── Dockerfile          
├── docker-compose.yml      
└── ...

Package.json file:

{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4",
    "pm2": "5.3.0"
  },
  "devDependencies": {
    "concurrently": "8.2.0",
    "babel-eslint": "^10.1.0",
    "eslint": "8.47.0"
  },
  "scripts": {
    "lint": "npx eslint --fix --ext .js,.jsx .",
    "server": "pm2 start src/App.js --watch npm -- start",
    "react": "export SET NODE_OPTIONS=--openssl-legacy-provider && CHOKIDAR_USEPOLLING=true react-scripts start",
    "start": "concurrently \"npm run lint\" \"npm run react\" \"npm run server \" ",
    "build": "export SET NODE_OPTIONS=--openssl-legacy-provider && react-scripts build",
    "test": "export SET NODE_OPTIONS=--openssl-legacy-provider && react-scripts test",
    "eject": "export SET NODE_OPTIONS=--openssl-legacy-provider && react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Dockerfile.frontend file:

FROM node:18.3.0-alpine
WORKDIR /frontend
ENV PATH /frontend/node_modules/.bin:$PATH

COPY ./frontend/package*.json ./
COPY ./frontend/ecosystem.config.js ./

RUN npm install --silent && \
    npm install react-scripts@5.0.1 -g --silent && \
    npm install pm2 -g && \
    npm install eslint babel-eslint --save-dev && \
    npm i -g concurrently && \
    npm ci \
    && npm cache clean --force \
    && mv /frontend/node_modules /node_modules

COPY ./frontend ./
EXPOSE 3000
RUN ls -al -R

CMD ["pm2", "reload", "all", \
     "pm2-runtime", "npm", "run", "start", "ecosystem.config.js", "--watch"]

ecosystem.config.js file:

module.exports = {
    apps : [{
      name: "app",
      script: "./src/App.js",
      "watch": true,
      "autorestart": true,
      "ignore_watch": ["node_modules"],
      env: {
        NODE_ENV: "development",
      },
      env_production: {
        NODE_ENV: "production",
      }
    }]
}

docker-compose file:

version: "3.9"
services:
  app: # pass
  db: # pass
  frontend:
    container_name: frontend-dev
    build:
      context: . 
      dockerfile: ./frontend/Dockerfile.frontend
    restart: always
    ports:
      - 3000:3000
    volumes:
      - ./frontend:/app
      - ./frontend/node_modules:/app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true
      - WATCHPACK_POLLING=true
      - FAST_REFRESH=true
      - WDS_SOCKET_PORT=0
      - NODE_ENV=development
      - PORT=3000
    command: ["npm", "run", "start"]

I appreciate any help or suggestions to resolve this issue. Thank you in advance!

Logs after running docker-compose up:

frontend-dev         | \> frontend@0.1.0 start
frontend-dev         | \> concurrently "npm run lint" "npm run react" "npm run server "
frontend-dev         | \[1\]
frontend-dev         |
frontend-dev         | \[1\] \> frontend@0.1.0 react
frontend-dev         | \[1\] \> export SET NODE_OPTIONS=--openssl-legacy-provider && CHOKIDAR_USEPOLLING=true react-scripts start
frontend-dev         | \[1\]
frontend-dev         |
frontend-dev         | \[0\]
frontend-dev         | \[0\] \> frontend@0.1.0 lint
frontend-dev         | \[0\] \> npx eslint --fix --ext .js,.jsx .
frontend-dev         | \[0\]
frontend-dev         | \[2\]
frontend-dev         |
frontend-dev         | \[2\] \> frontend@0.1.0 server
frontend-dev         | \[2\] \> pm2 start src/App.js --watch npm -- start
frontend-dev         | \[2\]
frontend-dev         | \[2\]                         -------------
frontend-dev         | \[2\]
frontend-dev         | \[2\]                           Runtime Edition
frontend-dev         | \[2\]
frontend-dev         | \[2\]         PM2 is a Production Process Manager for Node.js applications
frontend-dev         | \[2\]                      with a built-in Load Balancer.
frontend-dev         | \[2\]
frontend-dev         | \[2\]                 Start and Daemonize any application:
frontend-dev         | \[2\]                 $ pm2 start app.js
frontend-dev         | \[2\]
frontend-dev         | \[2\]                 Load Balance 4 instances of api.js:
frontend-dev         | \[2\]                 $ pm2 start api.js -i 4
frontend-dev         | \[2\]
frontend-dev         | \[2\]                 Monitor in production:
frontend-dev         | \[2\]                 $ pm2 monitor
frontend-dev         | \[2\]
frontend-dev         | \[2\]                 Make pm2 auto-boot at server restart:
frontend-dev         | \[2\]                 $ pm2 startup
frontend-dev         | \[2\]
frontend-dev         | \[2\]                 To go further checkout:
frontend-dev         | \[2\]                 http://pm2.io/
frontend-dev         | \[2\]
frontend-dev         | \[2\]
frontend-dev         | \[2\]                         -------------
frontend-dev         | \[2\]
frontend-dev         |
frontend-dev         | \[2\]
frontend-dev         | \[PM2\] Spawning PM2 daemon with pm2_home=/root/.pm2
app-dev              | Waiting for database...
app-dev              | Database available!
frontend-dev         | \[2\]
frontend-dev         | \[PM2\] PM2 Successfully daemonized
frontend-dev         | \[2\]
frontend-dev         | \[PM2\] Starting /frontend/src/App.js in fork_mode (1 instance)
frontend-dev         | \[2\] \[PM2\] Done.
frontend-dev         | \[2\]
frontend-dev         | ┌────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
frontend-dev         | \[2\] │ id │ name   │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
frontend-dev         | \[2\] ├────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
frontend-dev         | \[2\] │ 0  │ App    │ default     │ 0.1.0   │ fork    │ 132      │ 0s     │ 0    │ online    │ 0%       │ 25.5mb   │ root     │ enabled  │
frontend-dev         | \[2\] └────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
frontend-dev         | \[2\] npm run server  exited with code 0
app-dev              | Operations to perform:
app-dev              |   Apply all migrations: admin, auth, authtoken, contenttypes, core, sessions, token_blacklist
app-dev              | Running migrations:
app-dev              |   No migrations to apply.
frontend-dev         | \[1\]
frontend-dev         | (node:94) \[DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE\] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
frontend-dev         | \[1\] (Use `node --trace-deprecation ...` to show where the warning was created)
frontend-dev         | \[1\] (node:94) \[DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE\] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
frontend-dev         | \[1\] Starting the development server...
frontend-dev         | \[1\]
app-dev              | Watching for file changes with StatReloader
app-dev              | Performing system checks...
app-dev              |
app-dev              |
app-dev              | System check identified no issues (0 silenced).
frontend-dev         | \[0\] One of your dependencies, babel-preset-react-app, is importing the
frontend-dev         | \[0\] "@babel/plugin-proposal-private-property-in-object" package without
frontend-dev         | \[0\] declaring it in its dependencies. This is currently working because
frontend-dev         | \[0\] "@babel/plugin-proposal-private-property-in-object" is already in your
frontend-dev         | \[0\] node_modules folder for unrelated reasons, but it may break at any time.
frontend-dev         | \[0\]
frontend-dev         | \[0\] babel-preset-react-app is part of the create-react-app project, which
frontend-dev         | \[0\] is not maintianed anymore. It is thus unlikely that this bug will
frontend-dev         | \[0\] ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
frontend-dev         | \[0\] your devDependencies to work around this error. This will make this message
frontend-dev         | \[0\] go away.
frontend-dev         | \[0\]
frontend-dev         | \[0\] npm run lint exited with code 0
app-dev              | August 24, 2023 - 02:23:11
app-dev              | Django version 4.2.4, using settings 'app.settings'
app-dev              | Starting development server at http://0.0.0.0:8000/
app-dev              | Quit the server with CONTROL-C.
app-dev              |
app-dev              |
frontend-dev         | \[1\] One of your dependencies, babel-preset-react-app, is importing the
frontend-dev         | \[1\] "@babel/plugin-proposal-private-property-in-object" package without
frontend-dev         | \[1\] declaring it in its dependencies. This is currently working because
frontend-dev         | \[1\] "@babel/plugin-proposal-private-property-in-object" is already in your
frontend-dev         | \[1\] node_modules folder for unrelated reasons, but it may break at any time.
frontend-dev         | \[1\]
frontend-dev         | \[1\] babel-preset-react-app is part of the create-react-app project, which
frontend-dev         | \[1\] is not maintianed anymore. It is thus unlikely that this bug will
frontend-dev         | \[1\] ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
frontend-dev         | \[1\] your devDependencies to work around this error. This will make this message
frontend-dev         | \[1\] go away.
frontend-dev         | \[1\]
frontend-dev         | \[1\] Compiled successfully!
frontend-dev         | \[1\]
frontend-dev         | \[1\] You can now view frontend in the browser.
frontend-dev         | \[1\]
frontend-dev         | \[1\]   Local:            http://localhost:3000
frontend-dev         | \[1\]   On Your Network:  http://172.27.0.3:3000
frontend-dev         | \[1\]
frontend-dev         | \[1\] Note that the development build is not optimized.
frontend-dev         | \[1\] To create a production build, use npm run build.
frontend-dev         | \[1\]
frontend-dev         | \[1\] webpack compiled successfully
frontend-dev         | \[1\] Compiling...
frontend-dev         | \[1\] Compiled successfully!
frontend-dev         | \[1\] webpack compiled successfully

Solution

  • You are very close but your docker-compose needs up be updated to the following

    volumes:
          - ./frontend:/frontend  # whatever is defined WORKDIR in Dockerfile
          - ./frontend/node_modules:/frontend/node_modules  # unrelated but doesn't hurt to update
    

    This means everything in your current directory will be synced with the directory that you defined in Dockerfile WORKDIR, so the app that is running inside of your Docker container is able to actually get the updated file, notify the file watcher, re-transpile the app, and reloads the page.