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
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.