I have a Dockerfile to build a React app and copy the build to an Nginx container.
FROM node:14-buster-slim AS node-base
FROM node-base as deps
WORKDIR /app
COPY ./ui/package.json ./ui/yarn.lock ./ui/.npmrc ./
RUN yarn install --frozen-lockfile
FROM node-base as build-ui
WORKDIR /app
COPY ./ui ./
COPY --from=deps /app/package.json /app/yarn.lock /app/node_modules ./
RUN yarn run build
FROM nginx:1.21-alpine as ui-server
WORKDIR /usr/share/nginx/html
COPY --from=build-ui /app/build .
COPY ./ui/nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Locally, the build works without issue. In CI however, it fails to build:
#14 [build-ui 4/4] RUN yarn run build
#14 0.490 yarn run v1.22.17
#14 0.523 $ react-scripts build
#14 0.535 /bin/sh: 1: react-scripts: not found
#14 0.545 error Command failed with exit code 127.
#14 0.546 info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
#14 ERROR: process "/bin/sh -c yarn run build" did not complete successfully: exit code: 127
The reason it worked locally was because my local node_modules was copied into the container in the COPY ./ui ./ step.
Once I .dockerignore-d node_modules, the realized the COPY --from=deps /app/package.json /app/yarn.lock /app/node_modules ./ was copying the children of node_modules to the project root instead of the modules directory.
Changing the COPY step to the following resolved the issue:
COPY --from=deps /app/package.json /app/yarn.lock ./
COPY --from=deps /app/node_modules /app/node_modules
How do I configure this as a multi stage build
yarn start maps to
"start": "nodemon ./server-build/index.js",
Dockerfile
FROM node:14.17.3-alpine AS build
RUN apk add --no-cache build-base gcc autoconf automake libtool zlib-dev libpng-dev nasm
RUN npm i nodemon -g
WORKDIR /app/web-web
ENV PATH /app/web-web/node_modules/.bin:$PATH
COPY package.json /app/web-web/package.json
RUN yarn install --network-timeout 1000000000
COPY . /app/web-web
RUN yarn run build:staging
COPY . /app/web-web
EXPOSE 3006
CMD ["yarn", "start"]
Warning NOT Tested code.
The Dockerfile below is not tested and you must consider it as a starting point.
Missing some info, the apk packages are required only for build or else to run ?
I suggest you read about multi-stage build
###############################################################
# This stage do:
# 1. Install required software
# 2. Copy package.json and install deps
###############################################################
FROM node:14.17.3-alpine AS build
# This packet are required ONLY for build ??? Otherwise add on final stage
RUN apk add --no-cache build-base gcc autoconf automake libtool zlib-dev libpng-dev nasm
WORKDIR /app/web-web
ENV PATH /app/web-web/node_modules/.bin:$PATH
COPY package.json /app/web-web/package.json
RUN yarn install --network-timeout 1000000000
###############################################################
# This stage do:
# 1. copy node_modules from build stage to this image
# 2. build app and run
###############################################################
FROM node:14.17.3-alpine AS final
WORKDIR /app/web-web
# Magic append here: I copy from prev stage (build) to this one
COPY --from=build /app/web-web .
COPY . /app/web-web
RUN npm i nodemon -g
RUN yarn run build:staging
EXPOSE 3006
CMD ["yarn", "start"]
I am deploying a react app to Heroku via TravisCI. The fact that I'm using Heroku doesn't really affect what I'm about to ask, I'm pretty sure, it's just there for context. Travis successfully deploys the app until I add a testing step (the script section) in .travis.yml:
language: generic
sudo: required
services:
- docker
before_install:
- docker build -t myapp:prod -f Dockerfile.prod .
script:
- docker run -e CI=true myapp:prod npm run test
after_success:
- docker build -t myapp:prod -f Dockerfile.prod .
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_ID" --password-stdin
- docker push myapp:prod
deploy:
provider: heroku
app: myapp
skip_cleanup: true
api_key:
secure: <my_key>
However, my Dockerfile.prod is a multi-stage node + nginx where the nginx stage doesn't keep any node or npm stuff:
# build environment
FROM node:13.12.0-alpine as builder
# set working directory
WORKDIR /app
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install app dependencies
COPY package.json ./
COPY package-lock.json ./
# some CI stuff I guess
RUN npm ci
RUN npm install react-scripts#3.4.1 -g --silent
COPY . ./
RUN npm run build
# production environment
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
# If using React Router
COPY --from=builder /app/build /usr/share/nginx/html
# For Heroku
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
Therefore, it is my understanding that .travis.yml tries to run that npm run test command inside my nginx container and can't execute npm commands (no node installed, right?). So guided by SO answers such as this one I started adding commands into that nginx stage such as
COPY package.json ./
COPY package-lock.json ./
RUN apk add --update npm
but I realized I might be approaching this the wrong way. Should I perhaps be adding npm through Travis? That is, should I include in .travis.yml in the scripts section something like docker run -e CI=true myapp:prod apk add --update npm and whatever else is necessary? This would result in a smaller nginx image no? However, would I run into problems with package.json from the node stage in Dockerfile.prod or anything like that?
In summary, to use TravisCI to test a dockerized react app served with nginx, at what point should I install npm into my image? Does it happen as part of script in .travis.yml or does it happen in Dockerfile.prod? If it is recommened to npm run tests inside Dockerfile.prod, would I do that in the first stage (node) or the second (nginx)?
Thanks
EDIT: Not sure if this can be considered solved, but a user on Reddit recommended to simply RUN npm run test right before the RUN npm run build.
Our applications uses webpack. I need all my tests to run and artifacts to be created by web pack;
In my package.json, "build" script is defined:
..
"build": "webpack --config webpack.config.prod.js -p"
..
and this is my gitlab-ci.yml for now:
image: iteamdev/node-webpack:latest
variables:
NODE_ENV: "development" # required, because we need to install devDependencies
stages:
- build
compile:
stage: build
script:
- npm -v
- npm install -qs # install all dependencies (and devDependencies)
- npm run build # run webpack, set NODE_ENV=production
artifacts:
paths:
- dist/
What options do I have for the "image"? Is the one i'm using good for my needs? Should I work with a specific version?
I'd probably recommend using image: node:4.2.2 for your image.
I was wondering if anyone had any experience using create-react-app with docker. I was able to get it set up with a Dockerfile like:
from node
RUN mkdir /src
WORKDIR /src
ADD package.json /src/package.json
RUN npm install
EXPOSE 3000
CMD [ "npm", "start" ]
And then used a docker-compose file like:
app:
volumes:
- "./app:/src"
ports:
- "3000:3000"
- "35729:35729"
build: ./app
This allowed me to start up the container and view the app. However livereload didn't work when saving files in the mounted volume and webpack created several .json.gzip files in the src directory.
Any suggestions for getting this working correctly?
Yeah, as aholbreich mentioned, I'd use npm install / npm start locally on my machine for development, just because it's so easy. It's probably possible with docker-compose, mounting volumes etc. too, but I think it could be a bit fiddly to set up.
For deployment you can then very easily use a Dockerfile. Here's an example Dockerfile I'm using:
FROM node:6.9
# Create app directory
RUN mkdir -p /src/app
WORKDIR /src/app
# to make npm test run only once non-interactively
ENV CI=true
# Install app dependencies
COPY package.json /src/app/
RUN npm install && \
npm install -g pushstate-server
# Bundle app source
COPY . /src/app
# Build and optimize react app
RUN npm run build
EXPOSE 9000
# defined in package.json
CMD [ "npm", "run", "start:prod" ]
You need to add the start:prod option to your package.json:
"scripts": {
"start": "react-scripts start",
"start:prod": "pushstate-server build",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
You can run the tests on your CI service with:
docker run <image> npm test
There's nothing stopping you from running this docker container locally as well to make sure things work as expected.
I recently made a small project called hello-docker-react who just does what the op is looking for.
It's made with docker-compose, create-react-app, yarn, a node image, and a small entrypoint script.
Live reload work flawlessly and I haven't found any problems yet.
https://github.com/lopezator/hello-docker-react
here is good gide for this
https://mherman.org/blog/dockerizing-a-react-app/
for development
# base image
FROM node:9.6.1
# set working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install react-scripts#1.1.1 -g --silent
# start app
CMD ["npm", "start"]
for production
# build environment
FROM node:9.6.1 as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install react-scripts#1.1.1 -g --silent
COPY . /usr/src/app
RUN npm run build
# production environment
FROM nginx:1.13.9-alpine
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Not exactly a direct improvement of the author's code, but I was able to get a development environment working with very little code - and no direct dependency to node on my machine - like this:
docker-compose.yml
services:
node:
image: node:16
user: "node"
command: "npm start"
working_dir: /app
volumes:
- ./:/app
ports:
- 3000:3000
This way, you avoid creating docker images from a Dockerfile.
Usage is generally like this:
install dependencies before running: docker compose run node npm install
run development environment: docker compose up
install new dependencies: docker compose run node npm install [package name]
clean up docker instances created with compose run: docker compose rm
While using docker in development with create-react-app, i discovered that it is possible to override the webpackDevServer configuration by adding CHOKIDAR_USEPOLLING=1to your .env file. This will make the file watching work again. It even refreshes the browser page on the host! The only thing that i discovered is that it doesn't open up a webpage automatically.
I can also advise to add tty: true to your service to have your original console output back into your terminal. To remove the container name prefixes in the logs, you can run something like this after running docker-compose up -d:
docker-compose logs -f --tail=100 client | cut -f2 -d \"|\""
Running with CRA 4.0 and many dependencies
.dockerignore
.git
.gitignore
node_modules
build
Dockerfile.dev
FROM node:alpine
WORKDIR /app
COPY package.json /app
RUN yarn install
COPY . .
CMD ["yarn", "start"]
docker-compose.dev.yml
version: "3.8"
services:
print:
stdin_open: true
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ".:/app"
- "/app/node_modules"
Dockerfile.prod
FROM node:alpine as build
WORKDIR /app
COPY package.json /app
RUN yarn install
COPY . /app
RUN yarn run build
FROM nginx:stable-alpine
COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/build /usr/share/nginx/html
docker-compose.prod.yml
version: "3.8"
services:
print:
stdin_open: true
build:
context: .
dockerfile: Dockerfile.prod
ports:
- "80:80"
nginx.conf
server {
listen 80;
server_name frontend;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri /index.html;
}
}
To run
docker-compose.exe -f .\docker-compose.yml up --build
or
docker-compose.exe -f .\docker-compose.dev.yml up --build
Here is a simple (pure docker) solution without local installation of runtime (e.g. node):
cd /tmp
docker run -it --rm -v "$PWD":/app -w /app node yarn create react-app my-app
sudo chown -R $USER:root my-app/
cd my-app
nano docker-compose.yml # see docker-compose.yml below
docker compose up -d
docker-compose.yml:
services:
node:
image: node:16-alpine
environment:
- CHOKIDAR_USEPOLLING=true
- FAST_REFRESH=true
working_dir: /app
ports:
- '3000:3000'
command: "yarn start"
volumes:
- './:/app'
open localhost:3000 in your browser. Hot reload should work out of the box.