How to run my react app in Docker container - node.js

I have this react app I want to dockerize. But the problem is, even though I tried, it doesn't work. But it works pretty well locally
This is how the current directories look like:
docker-compose.yaml
frontend_v2
- node_modules
- public
- src
- <react jsx files>
- Dockerfile
- package.json
- package-lock.json
So this is the content of the above Dockerfile:
# Use an official node runtime as a parent image
FROM node:latest
WORKDIR /app
# Install dependencies
COPY package.json /app
RUN npm install
# Add rest of the client code
COPY . /app
EXPOSE 3000
CMD ["npm", "start"]
And I am using a docker-compose.yml to spin up the container, and this is how it looks like:
This is in the root directory.
version: "3.2"
services:
frontend:
build: ./frontend_v2
environment:
CHOKIDAR_USEPOLLING: "true"
volumes:
# - /app/node_modules
- ./frontend_v2/src:/app/src
ports:
- 80:3000
Problem I am facing is, even though the container is running I get the below error (which I don't get I run locally)
When i try
docker logs <exited-container-id> i get this below output
> cyberhr-rv18.0.4#0.1.0 start /app
> react-scripts start
There might be a problem with the project dependency tree.
It is likely not a bug in Create React App, but something you need to fix locally.
The react-scripts package provided by Create React App requires a dependency:
"webpack-dev-server": "3.11.0"
Don't try to install it manually: your package manager does it automatically.
However, a different version of webpack-dev-server was detected higher up in the tree:
/app/node_modules/webpack-dev-server (version: 3.10.3)
Manually installing incompatible versions is known to cause hard-to-debug issues.
If you would prefer to ignore this check, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project.
That will permanently disable this message but you might encounter other issues.
To fix the dependency tree, try following the steps below in the exact order:
1. Delete package-lock.json (not package.json!) and/or yarn.lock in your project folder.
2. Delete node_modules in your project folder.
3. Remove "webpack-dev-server" from dependencies and/or devDependencies in the package.json file in your project folder.
4. Run npm install or yarn, depending on the package manager you use.
In most cases, this should be enough to fix the problem.
If this has not helped, there are a few other things you can try:
5. If you used npm, install yarn (http://yarnpkg.com/) and repeat the above steps with it instead.
This may help because npm has known issues with package hoisting which may get resolved in future versions.
6. Check if /app/node_modules/webpack-dev-server is outside your project directory.
For example, you might have accidentally installed something in your home folder.
7. Try running npm ls webpack-dev-server in your project folder.
This will tell you which other package (apart from the expected react-scripts) installed webpack-dev-server.
If nothing else helps, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project.
That would permanently disable this preflight check in case you want to proceed anyway.
P.S. We know this message is long but please read the steps above :-) We hope you find them helpful!
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! cyberhr-rv18.0.4#0.1.0 start: `react-scripts start`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the cyberhr-rv18.0.4#0.1.0 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /root/.npm/_logs/2020-09-18T08_32_25_268Z-debug.log
[Update]
Here's my package.json file:
{
"name": "cyberhr-rv18.0.4",
"version": "0.1.0",
"private": true,
"homepage": "",
"dependencies": {
"3d-force-graph": "^1.60.11",
"#blueprintjs/core": "^3.26.1",
"#coreui/coreui": "^2.1.12",
"#coreui/coreui-plugin-chartjs-custom-tooltips": "^1.3.1",
"#coreui/icons": "0.3.0",
"#coreui/react": "^2.5.1",
"#emotion/core": "^10.0.35",
"#material-ui/core": "^4.11.0",
"#tensorflow/tfjs": "^1.7.2",
"#tensorflow/tfjs-tsne": "^0.2.0",
"axios": "^0.19.2",
"bootstrap": "^4.3.1",
"chart.js": "^2.8.0",
"classnames": "^2.2.6",
"core-js": "^3.1.4",
"d3-dsv": "^1.2.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"evergreen-ui": "^4.27.4",
"flag-icon-css": "^3.3.0",
"font-awesome": "^4.7.0",
"google-maps-react": "^2.0.2",
"jquery": "^3.4.0",
"material-dashboard-react": "^1.8.0",
"mdbreact": "4.25.3",
"node-sass": "^4.12.0",
"prop-types": "^15.7.2",
"react": "^16.8.4",
"react-animations": "^1.0.0",
"react-app-polyfill": "^1.0.1",
"react-bootstrap": "^1.0.0-beta.5",
"react-chartjs-2": "^2.7.6",
"react-chat-popup": "^1.1.2",
"react-dom": "^16.8.4",
"react-dropzone": "^11.0.1",
"react-force-graph": "^1.32.1",
"react-full-screen": "^0.2.4",
"react-loadable": "^5.5.0",
"react-nvd3": "^0.5.7",
"react-perfect-scrollbar": "^1.4.4",
"react-pure-grid": "^2.1.1",
"react-redux": "^6.0.1",
"react-reveal": "^1.2.2",
"react-router-config": "^5.0.1",
"react-router-dom": "^4.3.1",
"react-scripts": "^3.4.3",
"react-spinners": "^0.9.0",
"react-step-wizard": "^5.3.2",
"react-table": "^7.2.1",
"react-test-renderer": "^16.8.6",
"react-toastify": "^6.0.8",
"react-window-size": "^1.2.2",
"reactstrap": "^8.0.0",
"redux": "^4.0.1",
"simple-line-icons": "^2.4.1",
"tabler-react": "^2.0.0-alpha.1",
"three": "latest",
"tsne-js": "^1.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"devDependencies": {
"react-to-print": "^2.1.1"
}
}
Can someone please help me?

Firstly, make sure you are copying the same package-lock.json file that you use to install deps locally, to make sure you have the same dependency tree in your container as you do locally.
COPY package.json package-lock.json /app/
Then, make sure that you are matching the same node/npm version as you run locally (replace 12 with the major version you are running, be it 10, 12, 14 or whatever):
FROM node:12
Each node version is bundled with a specific npm version (latest 12 version comes with npm 6.14.6), you can find the bundled NPM version int he changelogs, https://github.com/nodejs/node/tree/master/doc/changelogs
Additionally, instead of running npm install, you might wanna run npm ci in the container. The latter skips any checks for discrepancy between the lock file and your package.json file and just installs the locked dependency tree, which is not only faster, but will also match your local dep tree exactly.
EDIT:
In addition, this line:
COPY . /app
Would also overwrite node_modules unless you have ignored it via .dockerignore or similar.
Easiest would probably be to add a .dockerignore file to the same folder as your Dockerfile and add lines stating:
.git
node_modules
The complete Dockerfile would look like this:
# Use whatever version you are running locally (see node -v)
FROM node:12
WORKDIR /app
# Install dependencies (you are already in /app)
COPY package.json package-lock.json ./
RUN npm ci
# Add rest of the client code
# .dockerignore needs to skip node_modules
COPY . /app
EXPOSE 3000
CMD ["npm", "start"]

So here
# Install dependencies
COPY package.json /app
RUN npm install
at the end of these steps you will have a NEW package-lock.json file and node_modules folder in the container build environment.
But then the posted Dockerfile has:
# Add rest of the client code
COPY . /app
and quite possibly -- WHAMMO! -- they're gone. Whatever OLD package-lock.json file or node_modules directory was in the . folder on the build machine will overwrite the ./app folder in the container -- including those NEW files just created in the container. Or perhaps end up with some unholy mix of both in the node_modules directory.
I take a different approach. I get everything working as I want it, and use this Dockerfile that lives in the directory with the source code:
FROM node:alpine # as of 2020-09 this runs node 14; also uses sh not bash
COPY . /app
WORKDIR /app
USER node
EXPOSE 8888 # change this to the port(s) where your app listens
CMD [ "node", "./index.js" ]
This is copied from a production system that relies on Google Cloud Build to build the container for launch on GCP. Therefore, the environment on the execution system is going to be Linux, the same as on my developer system.
If you are going cross-platform development, this obviously won't work sometimes.

Related

How do we specify within a server environment which node.js start script to run?

This may be a dumb question, but I'm stumped.
I have 3 environments: Dev(local), Staging(remote) and Production(remote).
I'm writing a relatively simply Express App.
In my package.json file, I have specified start scripts for each of the 3 environments. Here's the full file:
{
"name": "test-app",
"version": "0.0.1",
"description": "test utility app",
"private": true,
"scripts": {
"start": "cross-env NODE_ENV=development nodemon ./bin/www",
"start:staging": "cross-env NODE_ENV=staging nodemon ./bin/www",
"start:production": "cross-env NODE_ENV=production && nodemon ./bin/www"
},
"devDependencies": {
"cross-env": "^7.0.3"
},
"dependencies": {
"axios": "^0.24.0",
"config": "^3.3.6",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"morgan": "~1.9.1",
"nodemon": "^2.0.15"
}
}
Now, my question is, how do I specify which start script should be used in each remote server environment?
I understand how to start the development environment, so ignore that and focus on Staging and Production.
For example, I want the start:staging to be executed in Staging and the start:production to be executed in Production.
I may be thinking about this incorrectly, so please let me know if I should be approaching the Staging and Production environments differently.
It just seems to me the Staging and Production environments would have no idea which environment they should operate as without me assigning them as one or the other somehow.
Maybe this is done as part of the workflow? I'm using github actions for build/deployment, fyi.
Update
So, I did a little more digging. In the workflow file, there's a build step:
name: npm install, build, and test
run: |
npm install
npm run build --if-present
npm run test --if-present
Would I simply add something like: npm run start:staging or npm run start:production as appropriate?
You basically want to run the scripts and don't know the commands to run, right?
So
npm run start:staging
On gh-actions
- name: run app on staging
run: npm run start:staging

Optimizing Dockerfile for smaller image size

I'm trying to build a docker image which is as small as possible. My project is codded in typescript.
For what I have at the moment, it works just fine, but I'm sure not if my implementation is even good. When I run docker build the image size comes up to 276.23MB.
The problem is that I have to call "tsc" to compile my code into JavaScript which is a dev-dependency.
This is my Dockerfile:
FROM node:16-alpine3.11 as build
WORKDIR /usr/src/app
COPY . /usr/src/app/
RUN \
npm install && \
npm run build
FROM node:16-alpine3.11
WORKDIR /app
COPY --from=build /usr/src/app .
CMD [ "npm", "start" ]
These are the scripts & dependencies in my package.json file:
{
...
"scripts": {
"test": "jest --coverage",
"test:watch": "jest --watch",
"start": "fastify start -l info dist/src/app.js",
"build": "npx rimraf /dist && tsc",
"prebuild": "ts-node scripts/clean",
"docker": "docker build -t barnes-biz/pokemon:1.0.0 .",
"predocker": "npm run build",
},
"dependencies": {
"fastify": "^3.0.0",
"fastify-autoload": "^3.3.1",
"fastify-cli": "^2.13.0",
"fastify-helmet": "^5.3.2",
"fastify-plugin": "^3.0.0",
"fastify-sensible": "^3.1.0",
"fastify-swagger": "^4.8.3",
"mongoose": "^5.13.5",
"weak-napi": "^2.0.2"
},
"devDependencies": {
"#types/jest": "^26.0.23",
"#types/node": "^15.0.0",
"#types/rimraf": "^3.0.1",
"#typescript-eslint/eslint-plugin": "^4.28.0",
"#typescript-eslint/parser": "^4.28.0",
"concurrently": "^6.0.0",
"cross-env": "^7.0.3",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"fastify-tsconfig": "^1.0.1",
"jest": "^27.0.5",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.3",
"ts-node": "^10.0.0",
"typescript": "^4.2.4"
}
}
I don't seem to understand how to do it. Can you help me? Do you also know some best practices that I should follow writing the Dockerfile or anything else? Thank you!
A couple of things:
You want to copy the output of the compiler, not the source files
You still need to install node_modules in the running container, but want to use the production flag to make sure dev dependencies are ignore. The docker file will then look something like this:
FROM node:16-alpine3.11 as build
WORKDIR /usr/src/app
COPY package*.json ./ # A small optimization to allow for caching in between Docker builds
RUN npm ci
COPY . /usr/src/app/
RUN npm run build
FROM node:16-alpine3.11
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY --from=build /usr/build/app . # This line will depend on your tsconfig
RUN ls -la
CMD [ "npm", "start" ]
You could alternatively prune your node_modules in the build image and copy that over as well.

Bitbucket Pipelines from Docker Image has Missing NPM Modules

Question
What is wrong with my Dockerfile or bitbucket-pipelines.yml? Why are modules missing from the bitbucket pipelines environment?
Error
When I try to npm run build my Vue2 project with webpack using Bitbucket Pipelines, I get errors regarding missing modules.
From Logs
npm run build
> people-is#1.0.0 build /opt/atlassian/pipelines/agent/build
> node build/build.js
module.js:549
throw err;
^
Error: Cannot find module 'cli-spinners'
Files
Here are the files for configuration.
Dockerfile - builds cportwine/people-is
FROM node:8.10.0
RUN npm install
RUN npm install -g firebase-tools
CMD [ "npm", "run", "build" ]
bitbucket-pipelines.yml
image:
name: cportwine/people-is
pipelines:
default:
- step:
script:
- npm run build
package.json
{
"name": "people-is",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "cportwine",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js",
"deploy": "firebase deploy --token $FIREBASE_TOKEN"
},
"dependencies": {
"rxjs": "^5.5.8",
"uuid": "^3.2.1",
"vue": "^2.5.16",
"vue-json-excel": "^0.1.9",
"vue-router": "^2.8.1",
"vue-rx": "^5.0.0",
"vuefire": "^1.4.5",
"vuetify": "^0.15.2"
},
"devDependencies": {
"autoprefixer": "^7.2.6",
"babel-core": "^6.22.1",
"babel-loader": "^7.1.4",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^2.3.2",
"connect-history-api-fallback": "^1.5.0",
"copy-webpack-plugin": "^4.5.1",
"css-loader": "^0.28.11",
"cssnano": "^3.10.0",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-html": "^4.0.2",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-standard": "^3.0.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.16.3",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.11.1",
"firebase": "^4.12.0",
"firebase-tools": "^3.17.7",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"opn": "^5.3.0",
"optimize-css-assets-webpack-plugin": "^2.0.0",
"ora": "^1.4.0",
"rimraf": "^2.6.0",
"semver": "^5.5.0",
"shelljs": "^0.7.6",
"url-loader": "^0.5.8",
"vue-loader": "^13.7.1",
"vue-style-loader": "^3.1.2",
"vue-template-compiler": "^2.5.16",
"vuex": "^2.5.0",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^1.12.2",
"webpack-hot-middleware": "^2.21.2",
"webpack-merge": "^4.1.2"
},
"engines": {
"node": ">=8.10.0",
"npm": ">= 5.6.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://chaddportwine#bitbucket.org/jahnelgroup/people-is.git"
},
"keywords": [],
"license": "ISC",
"homepage": "https://bitbucket.org/jahnelgroup/people-is#readme"
}
What I see
When I ls the node_modules folder in both environments, they do not match. Modules are missing from bitbucket pipelines.
local folder
people-is/node_modules
...
chalk
char-spinner
chardet
check-types
chokidar
chownr
cipher-base
circular-json
cjson
clap
class-utils
clean-css
cli-boxes
cli-cursor
cli-spinners
cli-table
cli-table2
cli-width
cliui
...
bitbucket folder
/opt/atlassian/pipelines/agent/build/node_modules
Woah, missing modules!
...
chalk
cli-cursor
co
...
What I have tried
I added a command to the bitbucket-pipelines.yml to npm install before I build.
bitbucket-pipelines.yml
image:
name: cportwine/people-is
pipelines:
default:
- step:
script:
- npm install
- npm run build
This adds some additional modules (like cli-spinners from the error) to /opt/atlassian/pipelines/agent/build/node_modules.
bitbucket folder
/opt/atlassian/pipelines/agent/build/node_modules
...
chalk
char-spinner
chardet
check-types
chokidar
chownr
cipher-base
circular-json
cjson
clap
class-utils
clean-css
cli-boxes
cli-cursor
cli-spinners
cli-table
cli-table2
cli-width
cliui
clone
clone-response
co
...
However, the build command still fails, due to a different missing module.
Error
> people-is#1.0.0 build /opt/atlassian/pipelines/agent/build
> node build/build.js
module.js:549
throw err;
^
Error: Cannot find module './_safeGet'
Solutions
I can now build the app, but I don't know why!
1 - Simplify the Dockerfile
I removed all the npm commands. Maybe the npm install commands were redundant? There was no advantage using the Docker Image to pre-install npm packages.
2 - Remove Node_Modules before install
Using the bitbucket-pipelines.yml, remove the node_modules folder, and then perform npm install -g npm and npm install and npm install -g firebase-tools.
File Changes
bitbucket-pipelines.yml (added lines)
image:
name: cportwine/people-is
pipelines:
default:
- step:
script:
- rm -r node_modules <---- remove
- npm install -g npm <---- install
- npm install <---- install
- npm install -g firebase-tools <---- install
- npm run build
Dockerfile (lines removed)
FROM node:8.10.0
<---- remove
CMD [ "npm", "run", "build" ]
Answer ?
I'm not sure why moving all the npm install stuff into the bitbucket.pipelines.yml solved my issue building the project. I thought Docker would enable me to define my environment, e.g., install a version of node/npm and firebase. And pipelines would "run" that.
If someone could explain what I am missing, I would accept that answer.
Answer
I received support from the Atlassian Team
Leave npm install -g firebase in the docker image.
Move npm install from the docker image to the
bitbucket-pipelines.yml file.
Reason
The node_modules folder was listed in .gitignore
tl;dr
My mistake - I forgot about .gitignore and how that affects the node_modules folder in source control, e.g., Bitbucket Pipelines.
I was looking at my local node_modules folder and building locally which worked.
However
The node_modules in source control, by design, is not in-sync with my local folder because it's included in the .gitignore file.
So
It was necessary for me to rm node_modules and npm install using the bitbucket-pipelines.yml. Now, BitPipes finds the modules I have installed locally.
This is sort of the point of maintaining the package.json, but I got confused.

webpack build script error

i'm trying to start my local development environment with webpack
and faced a problem with this scripts part
"scripts": {
"start": "npm run build",
"build": "webpack -d && copy /src/index.html /dist/index.html && webpack-dev-server --content-base src/ --inline",
"build:prod": "webpack -p && copy /src/index.html dist/index.html"
},
when i'm doing npm start I see 2 errors like this
and one more with just npm run build failed
what can be the reason for these errors ?
index.html exist at provided address
i though mb i missed something on my dependencies, but everything seems to on place
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.1",
"oracledb": "^1.13.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"socket.io": "^2.0.3"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"webpack": "^3.6.0",
"webpack-dev-server": "^2.9.1"
}
The relevant error is:
The syntax of the command is incorrect.
As you're chaining multiple commands, you should generally isolate them and test them individually to see which one fails, but because it's right after the webpack build output, it must be the second one.
copy /src/index.html /dist/index.html
copy is a Windows command and on Windows the command-line options start with a / instead of a - like on Unix. You're trying to use Unix style paths, which end up being recognised as options. Paths on Windows use \ (backslash) instead of / (forward slash). Even if it were Unix, you don't want to start the path with a /, because that would look in the root of your file system, not the current directory, and it would fail because it can't find the file, unless you coincidentally have such a file in the root of your file system.
The command should be:
copy src\index.html dist\index.html
This won't work on other operating systems and it's an additional step in the build system. As an alternative you can integrate it into webpack with the copy-webpack-plugin, or in your specific case you can let webpack generate the index.html for you with the html-webpack-plugin. An advantage of the html-webpack-plugin is that it automatically inserts <script> tags with the produced bundles (especially useful if you use hashes in your bundle names). This means that webpack would output everything you need for you application and you could just deploy your dist directory. As a bonus, it plays nicely with webpack-dev-server, so everything can be kept in memory and you don't need to generate the file beforehand to make it work.

Automatic rendering of changes made in code using Volumes concept (node app running in Docker VM)

Now the changes made in .Jade files are rendered automatically on web UI, however the code changes made in node.js files are not rendering, so far, here is my docker-compose.yml file
www:
build: .
volumes:
- ./:/app_ww
ports:
- "80:3000"
expose:
- "80"
and here is my dockerfile
FROM node:4.4.1
RUN mkdir -p app_ww
WORKDIR /app_ww
ADD package.json package.json
RUN npm install
ADD . .
CMD ["npm","start"]
I am not getting my required work done. i.e,
automatic rendering of change made in code. so, here is the list of volumes created in the system by building the repository via $docker-compose up --build :
$ docker volume ls
DRIVER VOLUME NAME
local app_ww
Package.JsOn file :
{
"name": "node_restapi",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"less-middleware": "~2.1.0",
"serve-favicon": "~2.3.0"
}
}
NOTE: The container is running fine but the concept of volumes is not working,
ISSUE: Mentioned in the subject above.
If you are using docker-machine, I suppose the volume is mounted from the "machine" you create and not the native file system. You have to jump through a couple of hoops to have a native fs directory mounted to the docker-machine instance. What OS are you on? This seems to be logged as an intermittent issue here: https://github.com/docker/compose/issues/2247
I've found the solution for the problem i was facing.
Here it is ,
npm install -g nodemon
Including nodemon in the docker file with -g (globally) flag , works perfectly for me.
Thankyou #JHarris, #Aditya, #ShanShan
Regards,
Muhammad Abubaker

Resources