How do I run a webpack build from a docker container? - node.js

The app I'm making is written in ES6 and other goodies is transpiled by webpack inside a Docker container. At the moment everything works from creating the inner directory, installing dependencies, and creating the compiled bundle file.
When running the container instead, it says that dist/bundle.js does not exist. Except if I create the bundle file in the host directory, it will work.
I've tried creating a volume for the dist directory at it works the first time, but after making changes and rebuilding it does not pick up the new changes.
What I'm trying to achieve is having the container build and run the compiled bundle. I'm not sure if the webpack part should be in the Dockerfile as a build step or at runtime since the CMD ["yarn", "start"] crashes but RUN ["yarn", "start"] works.
Any suggestions ands help is appreciated. Thanks in advance.
|_src
|_index.js
|_dist
|_bundle.js
|_Dockerfile
|_.dockerignore
|_docker-compose.yml
|_webpack.config.js
|_package.json
|_yarn.lock
docker-compose.yml
version: "3.3"
services:
server:
build: .
image: selina-server
volumes:
- ./:/usr/app/selina-server
- /usr/app/selina-server/node_modules
# - /usr/app/selina-server/dist
ports:
- 3000:3000
Dockerfile
FROM node:latest
LABEL version="1.0"
LABEL description="This is the Selina server Docker image."
LABEL maintainer="AJ alvaroo#selina.com"
WORKDIR "/tmp"
COPY ["package.json", "yarn.lock*", "./"]
RUN ["yarn"]
WORKDIR "/usr/app/selina-server"
RUN ["ln", "-s", "/tmp/node_modules"]
COPY [".", "./"]
RUN ["yarn", "run", "build"]
EXPOSE 3000
CMD ["yarn", "start"]
.dockerignore
.git
.gitignore
node_modules
npm-debug.log
dist
package.json
{
"scripts": {
"build": "webpack",
"start": "node dist/bundle.js"
}
}

I was able to get a docker service in the browser with webpack by adding the following lines to webpack.config.js:
module.exports = {
//...
devServer: {
host: '0.0.0.0',
port: 3000
},
};
Docker seems to want the internal container address to be 0.0.0.0 and not localhost, which is the default string for webpack. Changing webpack.config.js specification and copying that into the container when it is being built allowed the correct port to be recognized on `http://localhost:3000' on the host machine. It worked for my project; hope it works for yours.

I haven't included my src tree structure but its basically identical to yours,
I use the following docker setup to get it to run and its how we dev stuff every day.
In package.json we have
"scripts": {
"start": "npm run lint-ts && npm run lint-scss && webpack-dev-server --inline --progress --port 6868",
}
dockerfile
FROM node:8.11.3-alpine
WORKDIR /usr/app
COPY package.json .npmrc ./
RUN mkdir -p /home/node/.cache/yarn && \
chmod -R 0755 /home/node/.cache && \
chown -R node:node /home/node && \
apk --no-cache add \
g++ gcc libgcc libstdc++ make python
COPY . .
EXPOSE 6868
ENTRYPOINT [ "/bin/ash" ]
docker-compose.yml
version: "3"
volumes:
yarn:
services:
web:
user: "1000:1000"
build:
context: .
args:
- http_proxy
- https_proxy
- no_proxy
container_name: "some-app"
command: -c "npm config set proxy=$http_proxy && npm run start"
volumes:
- .:/usr/app/
ports:
- "6868:6868"
Please note this Dockerfile is not suitable for production it's for a dev environment as its running stuff as root.
With this docker file there its a gotcha.
Because alpine is on musl and we are on glib if we install node modules on the host the compiled natives won't work on the docker container, Once the container is up if you get an error we run this to fix it (its a bit of a sticking plaster right now)
docker-compose exec container_name_goes_here /bin/ash -c "npm rebuild node-sass --force"
ikky but it works.

Try changing your start script in the package.json to perform the build first (doing this, you won't need the RUN command to perform the build in your Dockerfile:
{
"scripts": {
"build": "webpack",
"start": "webpack && node dist/bundle.js"
}
}

Related

docker-compose setup node app - deep dive

I am getting a npm ERR! enoent ENOENT: no such file or directory, open '/usr/src/app/package.json' error currently with the below docker setup or a error TS2307: Cannot find module 'Actions' or its corresponding type declarations- i think its a case that the paths are not found in tsconfig.json during the build or i am not COPYing the correct directory/volume as part of the Dockerfile. Have spent multiple days working through different path configs / setups, any help getting this to build would be greatly appreciated.
Would love to see a node / TS / docker / mysql project example if there are any in the community to share - have found it difficult to find opensource projects to compare this to for hints.
...
"paths": {
"Actions/*": [
"Actions/*"
],
}
docker-compose
version: '3.8'
services:
app:
image: app:latest
container_name: balanced-money-backend
build:
context: .
dockerfile: Dockerfile
# TODO investigate uid and gid, how does it get in - from a startup script? Think it needs to be added like user: $UID:$GID if my cmd calls a setup to id on host machine. Needs more investigation.
depends_on:
db:
condition: service_healthy
env_file:
- .env
restart: always
volumes:
- .:/var/www/
command: npm start
ports:
- $NODE_LOCAL_PORT:$NODE_DOCKER_PORT
environment:
- DB_HOST=$MYSQL_HOST
- DB_USERNAME=$MYSQL_USER
- DB_PORT=$MYSQL_DOCKER_PORT
- DB_PASSWORD=$MYSQL_PASSWORD
- DB_DATABASE=$MYSQL_DATABASE
db:
image: mysql:5.7
restart: always
container_name: balanced-money-database
environment:
- MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
- MYSQL_USER=$MYSQL_USER
- MYSQL_PASSWORD=$MYSQL_PASSWORD
- MYSQL_DATABASE=$MYSQL_DATABASE
ports:
- $MYSQL_LOCAL_PORT:$MYSQL_DOCKER_PORT
volumes:
- db:/var/lib/mysql
healthcheck: # mysql does not start immediatly, app needs to wait for mysql to start, having condition: service_healthy on app and a healthcheck makes sure db has started before app... i think.
test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
timeout: 20s
retries: 10
volumes:
db:
Dockerfile
############### Stage 1 - build the project
# use alpine version of node to keep the image size small as possible
FROM node:16-alpine AS build
# node docs recommend this
WORKDIR /usr/src/app
# docker caches per row as it builds, so copy those files which do not change often to the container first and following builds will not need copy as they are already cached by Docker.
COPY package*.json ./
COPY src tsconfig.json ./
RUN npm install
RUN npm run build
# TODO not sure about the stages - can i have a test / dev stage so test / dev is run in docker too.
############### Stage 2 - run the project
FROM build AS prod
EXPOSE 4000
# from stage 1, i.e. build take the code in the dist / package.json and copy to the container
COPY --from=build /usr/src/app/dist ./dist/
COPY --from=build /usr/src/app/package*.json ./
# npm ci will install exact versions from a package-lock file, and --production will only install dependencies, not dev dependencies.
RUN npm ci --production && npm cache clean --force
# make sure user is not root which could have security consequences.
USER node
CMD ["node", "dist/index.js"]
package.json scripts
"scripts": {
"build": "tsc",
"start": "node ./dist/index.js",
"node": "./dist/index.js",
"dev": "NODE_ENV=development DOTENV_CONFIG_PATH=.env.dev nodemon ts-node src/index.ts",
"format:prettier": "prettier --config .prettierrc 'src/**/*.ts' --write",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "DOTENV_CONFIG_PATH=.env.test NODE_ENV=test jest --runInBand",
"test:coverage": "DOTENV_CONFIG_PATH=.env.test NODE_ENV=test jest --coverage",
},

Dockerfile how to make start command different between dev and prod?

I have a TypeScript Node app. I have a dev and start npm scritps:
"dev": "ts-node-dev src/index.ts",
"build": "npm run test:ci && tsc",
"start": "node dist/index"
When developing I watch changes on the .ts files and when running in production I want to run the .js files from the dist dir (which is generated using the npm build script).
This is my Dockerfile:
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm i --only=prod
COPY . .
CMD ["npm", "run", "dev"]
When its running on dev env its good, but on production the CMD command should be like that:
CMD ["npm", "start"]
Also the RUN npm i --only-prod command also needs to be changed respectively.
How to make it adjustable to dev vs prod?
In kubernetes you can overwrite the default command args:
apiVersion: apps/v1
kind: Deployment
[...]
spec:
template:
spec:
containers:
- name: CONTAINER-NAME
image: IMAGE-NAME
args: [
"npm",
"start" ]
See the kubernetes documentation.
The detailed implementation depend on the deployment system you're using:
You can write two different .yaml files, one for the development and one for the production environment.
If you're deploying with helm, you can set this configuration in a value file per environment.
You can also use Kustomize as described in this example.

docker - image not updating after building

After I run docker-compose up -d --build, I run docker images, it shows:
REPOSITORY TAG IMAGE ID CREATED SIZE
test-tets-test-server_my-web latest 2a3f05e387a7 1 minutes ago 2.81GB
But When I run docker run -it 2a3f05e387a7 sh and look for the files, it seems that the files are not updating and still in old version.
Dockerfile
FROM node:lts-alpine
RUN npm install --global sequelize-cli nodemon
WORKDIR /server
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3030
CMD ["npm", "run", "dev"]
docker-compose.yml
version: '2.1'
services:
test-db:
image: mysql:5.7
...
test-web:
environment:
- NODE_ENV=local
- PORT=3030
build: .
command: >
./wait-for-db-redis.sh test-db npm run dev
volumes:
- ./:/server
ports:
- "3030:3030"
depends_on:
- test-db
package.json
...
"scripts": {
"test": "npm run lint && npm run mocha",
"lint": "eslint src/. test/. --config .eslintrc.json --fix",
"dev": "nodemon --legacy-watch src/",
"start": "node src/",
},
...
Since docker-compose up -d --build does not recreate, you may still see the old file, or they may be cache.
Run docker-compose up -d --build --force-recreate to force it recreate the image.

Nodejs syntax error inside docker but not outside

I have a super simple node.js file that runs perfectly fine outside of docker, but throws the following syntax error when I try to run it in a container:
./app/index.js: line 1: syntax error: unexpected word (expecting ")")
index.js is literally one line:
console.log("HI");
My Docker file is:
FROM node:14-alpine
WORKDIR /usr/app/
COPY . ./
CMD yarn start
I'm running the same command inside and out, same version of node etc. I'm on Windows 10 using the WSL 2 backed docker desktop. Could any of that have something to do with it? I'm going nuts...
Its working fine with me.
index.js
console.log("HI");
Dockerfile
FROM node:14-alpine
WORKDIR /usr/app/
COPY . ./
CMD yarn start
package.json
{
"name": "my-package",
"scripts": {
"start": "node index.js"
}
}
docker-compose.yml
version: '3.7'
services:
node_v14_test:
build:
context: .
dockerfile: Dockerfile
Then build and run docker container with docker compose.
docker-compose up --build
Result
try this
FROM node:14-alpine
WORKDIR /usr/app/
COPY . .
CMD [ "yarn", "start" ]
Seems like you need to convert file from dos to Linux issue or might the case image is using wrong code?
But the simplest way to debug that issue is not with Docker environment is to generate file inside Dcokerfile
FROM node:14-alpine
WORKDIR /usr/app/
COPY package.json .
RUN echo "console.log(\"HI\")" >> index.js
CMD yarn start

Docker /bin/bash: nodemon: command not found

I am trying to mount my working node code from my host into a docker container and run it using nodemon using docker-compose.
But container doesn't seems to be able to find nodemon.
Note: My host machine does not has node or npm installed on it.
Here are the files in the root folder of my project (test). (This is only a rough draft)
Dockerfile
FROM surenderthakran/nodejs:v4
ADD . /test
WORKDIR /test
RUN make install
CMD make run
Makefile
SHELL:=/bin/bash
PWD:=$(shell pwd)
export PATH:= $(PWD)/node_modules/.bin:$(PWD)/bin:$(PATH)
DOCKER:=$(shell grep docker /proc/1/cgroup)
install:
#echo Running make install......
#npm config set unsafe-perm true
#npm install
run:
#echo Running make run......
# Check if we are inside docker container
ifdef DOCKER
#echo We are dockerized!! :D
#nodemon index.js
else
#nodemon index.js
endif
.PHONY: install run
docker-compose.yml
app:
build: .
command: make run
volumes:
- .:/test
environment:
NODE_ENV: dev
ports:
- "17883:17883"
- "17884:17884"
package.json
{
"name": "test",
"version": "1.0.0",
"description": "test",
"main": "index.js",
"dependencies": {
"express": "^4.13.3",
"nodemon": "^1.8.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"api",
"nodejs",
"express"
],
"author": "test",
"license": "ISC"
}
index.js
'use strict';
var express = require('express');
I build my image using docker-compose build. It finishes successfully.
But when I try to run it using docker-compose up, I get:
Creating test_app_1...
Attaching to test_app_1
app_1 | Running make run......
app_1 | We are dockerized!! :D
app_1 | /bin/bash: nodemon: command not found
app_1 | make: *** [run] Error 127
test_app_1 exited with code 2
Gracefully stopping... (press Ctrl+C again to force)
Can anyone please advice?
Note: The Dockerfile for my base image surenderthakran/nodejs:v4 can be found here: https://github.com/surenderthakran/dockerfile_nodejs/blob/master/Dockerfile
The issue has been resolved. The issue boiled down to me not having node_modules in the mounted volume.
Basically, while doing docker-compose build the image was build correctly with the actual code being added to the image and creating the node_modules folder by npm install in the project root. But with docker-compose up the code was being mounted in the project root and it was overriding the earlier added code including the newly created node_modules folder.
So as a solution I compromised to install nodejs on my host and do a npm install on my host. So when the code my being mounted I still got my node_modules folder in my project root because it was also getting mounted from my host.
Not a very elegant solution but since it is a development setup I am ready for the compromise. On production I would be setting up using docker build and docker run and won't be using nodemon anyways.
If anyone can suggest me a better solution I will be greatful.
Thanks!!
I believe you should use a preinstall script in your package.json.
So, in the script section, just add script:
"scritpts": {
"preinstall": "npm i nodemon -g",
"start": "nodemon app.js",
}
And you should good to go :)
Pretty late for an answer. But you could use something called as named volumes to mount your node_modules in the docker volumes space. That way it would hide your bind mount.
You need to set the node_modules as a mounted volume in the docker container.
e.g
docker-compose.yml
app:
build: .
command: make run
volumes:
- .:/test
- /test/node_modules
environment:
NODE_ENV: dev
ports:
- "17883:17883"
- "17884:17884"
I've figured out how to do this without a Dockerfile, in case that's useful to anyone...
You can run multiple commands in the docker-compose.yml command line by using sh -c.
my-server:
image: node:18-alpine
build: .
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
working_dir: /usr/src/app
ports:
- "3100:3100"
command: sh -c "npm install -g nodemon && npm install && npm run dev "
environment:
NODE_ENV: development
PORT: 3100

Resources