Dockerfile env dependent starting point - node.js

Picking up docker, a little late to the show, but better late than never.
Following a few online tutorials i landed at a docker file and docker-compose for my 1st microservice node+mongo.
Terrible setup for dev so now will implement trusty pm2: https://dev.to/itmayziii/step-by-step-guide-to-setup-node-with-docker-2mc9
Production would want the below, but dev i would want pm2 instance mgr to reboot on file change..
But the obvious question i now have is how to differentiate between dev and prod in the Dockerfile?
Dockerfile
FROM node:12-alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
RUN npm i
COPY . /usr/src/app
EXPOSE 3000
CMD node ./build/server.js
docker-compose
version: "3"
services:
ms-authentication-service:
image: "ms-authentication-image"
depends_on:
- mongodb
build:
dockerfile: Dockerfile
context: .
links:
- mongodb
networks:
- default
ports:
- "8080:8000"
restart: always
mongodb:
image: mongo:4.2
container_name: "ms-authentication-mongo-image"
environment:
MONGO_INITDB_ROOT_USERNAME: bob
MONGO_INITDB_ROOT_PASSWORD: bob
networks:
- default
ports:
- 27017:27017

In general, managing environment like the staging or production based on ENV is common practice, but in the case of Docker the best approach is tag.
It's better to use tag for dev, stage and production in case of Docker. There are many reasons, one reason is mount code in development environment is fine but it is not recommended in the production environment.
When building images, always tag them with useful tags which codify
version information, intended destination (prod or test, for
instance), stability, or other information that is useful when
deploying the application in different environments. Do not rely on
the automatically-created latest tag.
Docker App development best-practices
But if still want to go with ENV approach then you can use the docker-entrypoint script.
Dockerfile
FROM node:alpine
RUN npm install pm2 -g
COPY . /app
WORKDIR /app
ENV NODE_ENV=development
RUN chmod +x docker-entrypoint.sh
ENTRYPOINT ["sh","docker-entrypoint.sh"]
Docker-entrypoint
#!bin/sh
if [ $NODE_ENV = development ]; then
pm2 start server.js
else
node server.js
fi
So you are good to go and you will able to change this in Dockerfile or run time
docker run --env NODE_ENV=production -it --rm node:production
or
docker run --env NODE_ENV=development -it --rm dev

Related

Docker network communication issue with multiple node server container-intercommunication

I'm trying to setup a docker environment in which multiple containers communicate with each other via REST. My simplified system architecture looks somewhat like this:
Let's say I have two services foo and bar, that run on a node server (NestJS projects). The Dockerfile for both look something like this:
FROM node:14-alpine
ENV NODE_ENV=production \
NPM_CONFIG_PREFIX=/home/node/.npm-global \
PATH=$PATH:/home/node/.npm-global/bin:/home/node/node_modules/.bin:$PATH
RUN apk add --no-cache tini
RUN apk add iputils
RUN mkdir -p /usr/src/app/node_modules
RUN chown -R node:node /usr/src/app
USER node
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
RUN npm ci --only=production
RUN npm cache clean --force
COPY --chown=node:node . ./
RUN ls -l
EXPOSE 80
ENTRYPOINT ["/sbin/tini", "--"]
CMD [ "npm", "start" ]
The docker-compose looks like this:
version: "3.8"
services:
foo:
image: "node:14-slim"
container_name: foo-service.${ENV}
build:
context: .
dockerfile: Dockerfile
args:
env: ${ENV}
port: ${PORT}
user: "node"
working_dir: /usr/src/app
environment:
- NODE_ENV=production
- VERSION=1.0
volumes:
- .:/usr/src/app
- /usr/app/node_modules
ports:
- ${PORT}:80
tty: true
command: "npm start"
networks:
my-network:
aliases:
- foo-service.${ENV}
networks:
my-network:
driver: "bridge"
name: my-network
I connect the containers via the my-network and made sure (docker network inspect my-network) that both containers share the same network. I can even ping one another (docker exec [foo] ping [bar])
When I'm running the application (making a REST call from the web interface) to foo, foo is then unable to "connect" to bar. I call bar from foo using the alias like this:
this.httpService.get('http://bar-service.dev:3002/...').
I get this error message:
I guess the containers still know each other since the DNS is resolved automatically by docker (I made sure that the IP-Address of bar is correct).
After some hours of trial and error and research I'd like to ask you guys. It might be an issue with alpine (some people had issues pinging node servers before). But it's also just as likely that I missed something important all along and can't seem to realize it...
Thanks in advance!

Run mocha unit-test on docker-compose images

I have an api in nodeJs, which run under micro-services.
So far it work, what i want to do next is to be able to run unit-test (mocha) on those images.
docker-compose.yml:
version: '3'
services: db:
image: mongo
ports:
- 27017:27017
db-seeds:
build: ./services/mongo-seeds
links:
- db
gateway:
build: ./services/gateway
ports:
- 4000:4000
depends_on:
- A
- B
- C
A:
build: ./services/A
ports:
- 4003:4000
depends_on:
- db
B:
build: ./services/B
ports:
- 4001:4000
depends_on:
- db
C:
build: ./services/C
ports:
- 4002:4000
depends_on:
- db
One of a DockerFile:
FROM node:latest
COPY . /src
WORKDIR /src
RUN yarn
EXPOSE 4000
CMD yarn start
What i did so far is to make another docker-compose file which will run other docker file (DockerFile.test) :
FROM node:latest
COPY . /src
WORKDIR /src
RUN yarn
EXPOSE 4000
CMD yarn test
also tried:
FROM node:latest
COPY . /src
WORKDIR /src
RUN yarn
EXPOSE 4000
CMD yarn start
RUN sleep 240
CMD yarn test
They both fail, at this point yarn test is launch before my gateway and servers are up. What i want to do, is to launch my servers and then run my unit-test in the images, but i'm new to docker and lack knowledge on how to implement that.
yarn test:
"test": "NODE_ENV=test ./node_modules/.bin/mocha"
You have to run mocha on the same host with your docker-compose file. This can be a docker image too (which can be started with docker-compose if you want ;)
You have to run docker-compose with your services before you run yarn test.
Create/Use a Dockerfile with docker and docker-compose and node (for your case)
copy your sources on it (docker-compose.yml and other relevant sources)
run docker-compose up -d on it
run yarn test on it
Tools like CloudFoundry or OpenStack might do this job.
If you are with CloudFoundry I can give you concrete details.
To concatenate several commands use the unix-cli syntax:
RUN docker-compose up -d && \
service1 && \
yarn test

Docker with nodemon does not reload my api when code changes

I've been working with docker some weeks ago and I was able to hold this issue, stop docker containers and start them over again to see the changes that I had made in my code but now is really anoying because every single change I do have to kill docker and then "docker-compose up".
However my friend is using the same container on his apple machine but when he makes changes to any server side code he does not have to restart his app.
I can see the changes when I go into the container but those changes are not reflected on live(browser).
My Dockerfile
FROM node:8.11.3
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
# Copy application files
COPY tools ./tools/
COPY migrations ./migrations/
COPY seeds ./seeds/
# Attempts to copy "build" folder even if it doesn't exist
COPY .env build* ./build/
RUN npm install -g nodemon
RUN git clone https://github.com/vishnubob/wait-for-it.git
EXPOSE 8080
CMD ["nodemon", "-L", "server"]
My docker-compose.yml
api:
build: ./
hostname: api
container_name: api
ports:
- "${APP_PORT}:3000"
volumes:
- ./:/usr/src/app
env_file:
- ".env"
command: node tools/run.js
Any sugestion?

Docker port forwarding for nodejs app

I'm having problems configuring docker for my nodejs app.
I have previously set up containers for both php and rails with port forwarding working flawlessly, but for this instance i can't seem to get it to work.
Running: docker ps, i get the following:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a60f9c82d600 29c7d94a8c58 "/bin/sh -c 'npm s..." 5 seconds ago Up 3 seconds 3000/tcp romantic_albattani
As you can see I'm not getting the usual: 0.0.0.0:3000->3000/tcp that I am expecting.
docker-compose ps gives:
Name Command State Ports
------------------------------
My docker-compose.yml:
web:
build: .
volumes:
- .:/app
volumes_from:
- box
ports:
- "3000:3000"
box:
image: busybox
volumes:
- /node_modules
My Docker file:
FROM node:8.7.0
# The base node image sets a very verbose log level.
ENV NPM_CONFIG_LOGLEVEL warn
WORKDIR /tmp
COPY package.json /tmp/
RUN npm install
WORKDIR /app
ADD . /app
RUN cp -a /tmp/node_modules /app/
#ENV PORT=3000
EXPOSE 3000
CMD npm start
I'm running the command: docker-compose up --build
Any help at this point is appreciated.
I don't know if a docker inspect would be useful, but if so, tell me and i will also post it.
Edit: Changed my Dockerfile to follow the answer.
Your docker-compose.yml file has bad formatting, since you are not getting any errors i will assume you pasted it here wrong, here is the version with the fixed indenting:
web:
build: .
volumes:
- .:/app
volumes_from:
- box
ports:
- "3000:3000"
box:
image: busybox
volumes:
- /node_modules
Your Dockerfile has a bug, you are missing the ENTRYPOINT and/or CMD stanzas, instead you are using the RUN stanza with the wrong intent, here is a working Dockerfile with the fix applied:
FROM node:8.7.0
# The base node image sets a very verbose log level.
ENV NPM_CONFIG_LOGLEVEL warn
WORKDIR /tmp
COPY package.json /tmp/
RUN npm install
WORKDIR /app
ADD . /app
RUN cp -a /tmp/node_modules /app/
#ENV PORT=3000
EXPOSE 3000
CMD npm start
Your Dockerfile halted the execution of docker-compose at the docker image building stage because of the RUN npm start which is a process that starts and listens until stopped (because you want it to start your node app and listen for connections) causing docker-compose to never finish the docker image creating step, let alone the other steps like creating the needed containers and finish the entire docker-compose runtime process.
In short:
When you use RUN it is meant to run a command do some work and return sometime to continue the building process, it should return and exit code of 0 and the process will move on to the next Dockerfile stanza, or return another exit code and the building process will fail with an error.
When you use CMD you tell the docker image what is the starting command of all the containers started from this image (it can also be overridden at run time with docker run). It is tightly related to the ENTRYPOINT stanza, but for basic usage you are safe with the default.
Further reading: ENTRYPOINT, CMD and RUN

Production vs Development Docker setup for Node (Express & Mongo) App

I'm attempting to convert a Node app to using Docker but running into a few issues/questions I'm unable to answer.
But for simplicity I've included some very basic example files to keep the question on target. In fact the example below merely links to a Mongo container but doesn't use it in the code to keep it even simpler.
Primarily, what Dockerfile and docker-compose.yml setup is required to successfully use Docker on a Node + Express + Mongo app on both local (OS X) development and for Production builds?
Dockerfile
FROM node:6.3.0
# Create new user to avoid using root - is this correct practise?
RUN useradd --user-group --create-home --shell /bin/false app
COPY package.json /home/app/code/
RUN chown -R app:app /home/app/*
USER app
WORKDIR /home/app/code
# Should this even be set here or use docker-compose instead?
# And should there be:
# - docker-compose.yml setting it to production by default
# - docker-compose.dev.yml setting it to production?
# Or reverse it? (docker-compose.prod.yml instead with default being development?)
# Commenting below out or it will always run as production
#ENV NODE_ENV production
RUN npm install
USER root
COPY . /home/app/code
# Running chown to ensure new 'app' user owns files
RUN chown -R app:app /home/app/*
USER app
EXPOSE 3000
# What CMD should be here to ensure development versus production is simple?
# Development - Restart server and refresh browser on file changes
# Production - Ensure uptime.
CMD ["npm", "start"]
docker-compose.yml
version: "2"
services:
web:
build: .
# I would normally use a .env file but for this example will set explicitly
# env_file: .env
environment:
- NODE_ENV=production
volumes:
- ./:/home/app/code
- /home/app/code/node_modules
ports:
- "3000:3000"
links:
- mongo
mongo:
image: mongo
ports:
- "27017:27017"
docker-compose.dev.yml
version: "2"
services:
web:
# I would normally use a .env file but for this example will set explicitly
# env_file: .env
environment:
- NODE_ENV=development
package.json
{
"name": "docker-node-test",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "nodemon app.js"
},
"dependencies": {
"express": "^4.14.0",
"mongoose": "^4.6.1",
"nodemon": "^1.10.2"
},
"devDependencies": {
"mocha": "^3.0.2"
}
}
1. How to handle the different NODE_ENV (dev, production, staging)?
This is my primary question and conundrum.
In the example I’ve used the NODE_ENV is set in the Dockerfile as production and there are two docker-compose files:
docker-compose.yml sets the defaults include NODE_ENV to production
docker-compose.dev.yml overrides the NODE_ENV and sets it to development
1.1. Is it advised to rather switch that order around and have development settings as the default and instead use a docker-compose.prod.yml for overrides?
1.2. How do you handle the node_modules directory?
I'm really not sure how to handle the node_modules directory at all between local development needs and then running for Production. (Perhaps I have a fundamental misunderstanding though?)
Edit:
I added a .dockerignore file and included the node_modules directory as a line. This ensures the node_modules dir is ignored during the copy, etc.
I then edited the docker-compose.yml to include the node_modules as a volume.
volumes:
- ./:/home/app/code
- /home/app/code/node_modules
I have also put the above change into the full docker-compose.yml at the start of the question for completeness.
Is this even a solution?
Doing the above ensured I could have my local development npm install included dev-dependencies. And when running docker-compose up it pulls in the production only node modules inside the Docker container (since the default docker-compose.yml is set to NODE_ENV=production).
But it seems the NODE_ENV set inside the 2 docker-compose files aren't taken into account when running docker-compose -f docker-compose.yml build :/ I expected it to send NODE_ENV=production but ALL of the node_modules are re-installed (including the dev-dependencies).
Do we instead use 2 Dockerfiles? (Dockerfile for Prod; Dockerfile.dev for local development)
(I feel like that is a fundamental piece of logic/knowledge I am missing in the setup)
2. Nodemon vs PM2
How would one use nodemon on the local development machine but PM2 on the Production build?
3. Should you create a user inside the docker containers and then set that user to be used in the Dockerfile?
It uses root user by default but I’ve not seen many articles talking about creating a dedicated user within the container. Am I correct in what I’ve done for security? I certainly wouldn’t feel comfortable running an app as root on a non-Docker build.
Thank you for reading. Any and all assistance appreciated :)
I can share my experience, not saying it is the best solution.
I have Dockerfile and dockerfile.dev. In dockerfile.dev I install nodemon and run the app with nodemon, the NODE_ENV doesn't seem to have any impact. As for users you should not use root for security reasons. My dev version:
FROM node:16.14.0-alpine3.15
ENV NODE_ENV=development
# install missing libs and python3
RUN apk update && apk add -U unzip zip curl && rm -rf
/var/cache/apk/* && npm i node-gyp#8.4.1 nodemon#2.0.15 -g
WORKDIR /node
COPY package.json package-lock.json ./
RUN mkdir /app && chown -R node:node .
USER node
RUN npm install && npm cache clean --force
WORKDIR /node/app
COPY --chown=node:node . .
# local development
CMD ["nodemon", "server.js" ]
in Production I run the app with node:
FROM node:16.14.0-alpine
ENV NODE_ENV=production
# install missing libs and python3
RUN apk update && apk add -U unzip zip curl && rm -rf /var/cache/apk/* \
&& npm i node-gyp#8.4.1 -g
WORKDIR /node
COPY package.json package-lock.json ./
RUN mkdir /app && chown -R node:node .
USER node
RUN npm install && npm cache clean --force
WORKDIR /node/app
COPY --chown=node:node . .
CMD ["node", "server.js" ]
I have two separate versions of docker-compose. In docker-compose.dev.yml I set the dockerfile to dockerfile.dev:
app:
depends_on:
mongodb:
condition: service_healthy
build:
context: .
dockerfile: Dockerfile.dev
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:5000" ]
interval: 180s
timeout: 10s
retries: 5
restart: always
env_file: ./.env
ports:
- "5000:5000"
environment:
...
volumes:
- /node/app/node_modules
In production docker-compose.yml there is the dockerfile set to Dockerfile.
Nodemon vs PM2. I used pm2 before dockerizing the app. I cannot see any benefit of having it in docker, the restart: always takes care about restarting on error. You should better use restart: unless_stopped but I prefer the always option. Initially I used nodemon also on production so that the app reflected the volumes changes but I skipped this because the restart didn't work well (it was waiting for some code changes..).
Users: You can see it in my example. I took a course for docker + nodejs and setting a non-root user was recommended so I do it and I have no problems.
I hope I explained well enough and it can help you. Good luck.
Either, it doesn't matter too much, I prefer to have development details then overwrite with production details.
I don't commit them to my repo, then I have "npm install" in my dockerfile.
You can set rules in the dockerfile to which one to build based on build settings.
It is typical to build everything via root, and run the main program via root. You can set up other users, but for most uses it is not needed as the idea of docker containers is to isolate each process in individual docker containers.

Resources