Optimizing Dockerfile for smaller image size - node.js

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.

Related

sqlite3 with docker throws MODULE_NOT_FOUND error

My node application works on my local(MacOS), but it does not work if I use docker.
it works if I try local:
npm install
npm start
It throws error if I try docker
docker-compose build
docker-compose up
I'm getting this error.
Error: Cannot find module '/src/node_modules/sqlite3/lib/binding/napi-v6-linux-musl-x64/node_sqlite3.node'
Package.json
{
"name": "api",
"version": "1.0.0",
"description": "API",
"main": "index.js",
"scripts": {
"test": "rm -rf usersdb.sqlite && jest --forceExit",
"test:coverage": "npm run test -- --coverage --forceExit",
"start": "nodemon app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.1",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
"mysql": "^2.18.1",
"nodemon": "^2.0.20",
"sqlite3": "^5.1.4"
},
"devDependencies": {
"jest": "^28.1.1",
"supertest": "^6.3.3"
}
}
docker-compose.yml
version: "3.7"
services:
api:
image: test/api
build: ./
command: npm start
restart: on-failure
environment:
SERVER_PORT: 3004
TOKEN_KEY: test123
volumes:
- .:/src
ports:
- "3004:3004"
Dockerfile
FROM node:12.22-alpine as base
WORKDIR /src
COPY package*.json ./
EXPOSE 3004
RUN apk add --no-cache python2 g++ make
RUN npm install
FROM base as dev
ENV NODE_ENV=development
RUN npm install -g nodemon
COPY . ./
CMD ["nodemon", "app.js"]
Delete the volumes: block from your docker-compose.yml.
The volumes: block in your docker-compose.yml file is overwriting your entire application with content from your host system. That includes overwriting the Linux-OS node_modules tree with the MacOS version from your host system.
You don't need this volumes: block. The code and node_modules: tree are built into your image. If you need to develop your application, you can install Node on your host system (on MacOS this might be as little as brew install node) and use that for day-to-day development, even if you're planning to eventually use Docker for final deployment or if you have dependencies that run in containers.

Docker-Compose fail

I'm working with docker, docker-compose, trying to create an image but I'm getting an issue that I can't fix, I have tried a lot of ways but nothing seems to work, this configuration for my image, this is my docker file
FROM node:16-alpine#sha256:53ebfa5e6df1e458b47f677cb4f6fd3cf1d079083647dc40d182910a57b5a63d
RUN apk add dumb-init
ENV PORT=443
ENV NODE_ENV=development
ENV DBUSER=user
ENV DBPASS=pass
WORKDIR /app
COPY --chown=node:node . /app
RUN npm ci --only=prod
USER node
CMD ["dumb-init", "node", "/app/index.js"]
And my docker-compose.yaml looks like this:
version: '1.7'
services:
web:
build:
context: .
dockerfile: ./Dockerfile.web
ports:
- "9000:443"
extra_hosts:
"host.docker.internal": host-gateway
.dockerignore:
Dockerfile*
install.sh
README.md
docs/
dev/
var/db/
var/db.tar.gz
node_modules
npm-debug.log
.dockerignore
.git
.gitignore
.npmrc
package.json
"dependencies": {
"#peculiar/webcrypto": "^1.1.7",
"bcrypt": "^5.0.1",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.17.1",
"fs": "0.0.1-security",
"fs.promises": "^0.1.2",
"https": "^1.0.0",
"rethinkdb": "^2.4.2",
"uuid": "^8.3.2",
"ws": "^8.2.2"
},
"devDependencies": {
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.4",
"lint-staged": "^12.3.3",
"prettier": "^2.5.1"
},
And this is the error that I'm getting when I run docker-compose build:

Can't Dockerize my simple typescript website

I am trying to get a Typescript project running in Docker. I created the Docker file based on a tutorial but when I run it I get:
/usr/local/bin/docker-entrypoint.sh: 8: exec: .: Permission denied
I am not sure how to diagnosis this problem.
I can enter the container with
docker run --rm -it d3ca97c88aec bash -il
and run the server with
npm start
I have no file called docker-entrypoint.sh and I didn't make one.
I STRONGLY FEEL THIS IS BECAUSE TYPESCRIPT MAKES A BUILD FOLDER AND THAT BUILD FOLDER IS PERMISSIONED DIFFERENTLY.
Dockerfile
FROM node:12
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm#5+)
COPY package*.json ./
RUN npm install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
ENV COUCH_URL=NotSafeForStackOverflow
RUN npx tsc
EXPOSE 8080
CMD npx ts-node index.ts
{
"name": "express-react-app",
"version": "1.0.0",
"main": "index.js",
"repository": "",
"author": "",
"license": "",
"private": null,
"dependencies": {
"agentkeepalive": "^4.1.3",
"apollo-utilities": "^1.3.4",
"base64-arraybuffer": "^0.2.0",
"express": "^4.17.1",
"express-graphql": "^0.11.0",
"graphql": "^15.3.0",
"graphql-depth-limit": "^1.1.0",
"graphql-tag": "^2.11.0",
"graphql-type-json": "^0.3.2",
"i": "^0.3.6",
"merge-graphql-schemas": "^1.7.8",
"nano": "^8.2.2",
"node-webcrypto-ossl": "^2.1.1",
"npm": "^6.14.8",
"text-encoding": "^0.7.0",
"ts-node": "^9.0.0",
"typescript": "^3.7.4",
"uuid": "^8.3.0"
},
"scripts": {
"start": "npx ts-node index.ts",
"build": "npx tsc",
"dev": "nodemon --watch '**/*.ts' --exec 'ts-node' index.ts"
},
"devDependencies": {
"#types/express": "^4.17.7",
"#types/graphql": "^14.5.0",
"#types/node": "^14.6.3"
}
}
when you run CMD npx ts-node index.ts
it will overwrite default CMD [ "node" ]
please refer link below to how to use node image :
https://github.com/nodejs/docker-node/blob/master/README.md#how-to-use-this-image
you shoud be able done by using docker-compose :
version: "2"
services:
node:
image: "node:8"
user: "node"
working_dir: /home/node/app
environment:
- NODE_ENV=production
volumes:
- ./:/home/node/app
expose:
- "8081"
command: "npm start"

How to run my react app in Docker container

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.

Why is Docker suddenly trying to open docker-entrypoint.sh?

I have dockerised a Node.js app for my Ubuntu server. The Dockerfile is incredibly simple, and had been working fine. Suddenly, on my Ubuntu server, I'm now seeing this error:
sudo docker run -p 80:8080 username/test:test
/bin/sh: 0: Can't open /usr/local/bin/docker-entrypoint.sh
My Dockerfile looks like this:
FROM node:12
# Create the app directory
WORKDIR /Tap2Tap/TapServer
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
EXPOSE 8080
CMD ["npm", "start"]
There is no reference to this docker-entrypoint.sh and I don't know why it seems to have spontaneously stopped working. Any help would be much appreciated.
Best,
Peter
package.json file is:
{
"name": "Tap2Tap",
"version": "0.0.0",
"scripts": {
"start": "node ./bin/www.js"
},
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1",
"helmet": "^3.22.0",
"https": "^1.0.0",
"mqtt": "^4.0.1",
"pg": "^8.0.3",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.4.2"
}

Resources