Use docker run command to pass arguments to CMD in Dockerfile - node.js

I'm new to Docker and I'm having a hard time to setup the docker container as I want. I have a nodejs app can take two parameters when start. For example, I can use
node server.js 0 dev
or
node server.js 1 prod
to switch between production mode and dev mode and determine if it should turn the cluster on. Now I want to create docker image with arguments to do the similar thing, the only thing I can do so far is to adjust the Dockerfile to have a line
CMD [ "node", "server.js", "0", "dev"]
and
docker build -t me/app . to build the docker.
Then docker run -p 9000:9000 -d me/app to run the docker.
But If I want to switch to prod mode, I need to change the Dockerfile CMD to be
CMD [ "node", "server.js", "1", "prod"] ,
and I need to kill the old one listening on port 9000 and rebuild the image.
I wish I can have something like
docker run -p 9000:9000 environment=dev cluster=0 -d me/app
to create an image and run the nodejs command with "environment" and "cluster" arguments, so I don't need to change the Dockerfile and rebuild the docker any more. How can I accomplish this?

Make sure your Dockerfile declares an environment variable with ENV:
ENV environment default_env_value
ENV cluster default_cluster_value
The ENV <key> <value> form can be replaced inline.
Then you can pass an environment variable with docker run. Note that each variable requires a specific -e flag to run.
docker run -p 9000:9000 -e environment=dev -e cluster=0 -d me/app
Or you can set them through your compose file:
node:
environment:
- environment=dev
- cluster=0
Your Dockerfile CMD can use that environment variable, but, as mentioned in issue 5509, you need to do so in a sh -c form:
CMD ["sh", "-c", "node server.js ${cluster} ${environment}"]
The explanation is that the shell is responsible for expanding environment variables, not Docker. When you use the JSON syntax, you're explicitly requesting that your command bypass the shell and be executed directly.
Same idea with Builder RUN (applies to CMD as well):
Unlike the shell form, the exec form does not invoke a command shell.
This means that normal shell processing does not happen.
For example, RUN [ "echo", "$HOME" ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: RUN [ "sh", "-c", "echo $HOME" ].
When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

Another option is to use ENTRYPOINT to specify that node is the executable to run and CMD to provide the arguments. The docs have an example in Exec form ENTRYPOINT example.
Using this approach, your Dockerfile will look something like
FROM ...
ENTRYPOINT [ "node", "server.js" ]
CMD [ "0", "dev" ]
Running it in dev would use the same command
docker run -p 9000:9000 -d me/app
and running it in prod you would pass the parameters to the run command
docker run -p 9000:9000 -d me/app 1 prod
You may want to omit CMD entirely and always pass in 0 dev or 1 prod as arguments to the run command. That way you don't accidentally start a prod container in dev or a dev container in prod.

Option 1) Use ENV variable
Dockerfile
# we need to specify default values
ENV ENVIRONMENT=production
ENV CLUSTER=1
# there is no need to use parameters array
CMD node server.js ${CLUSTER} ${ENVIRONMENT}
Docker run
$ docker run -d -p 9000:9000 -e ENVIRONMENT=dev -e CLUSTER=0 -me/app
Option 2) Pass arguments
Dockerfile
# use entrypoint instead of CMD and do not specify any arguments
ENTRYPOINT node server.js
Docker run
Pass arguments after docker image name
$ docker run -p 9000:9000 -d me/app 0 dev

The typical way to do this in Docker containers is to pass in environment variables:
docker run -p 9000:9000 -e NODE_ENV=dev -e CLUSTER=0 -d me/app

Going a bit off topic, build arguments exist to allow you to pass in arguments at build time that manifest as environment variables for use in your docker image build process:
$ docker build --build-arg HTTP_PROXY=http://10.20.30.2:1234 .

Late joining the discussion. Here's a nifty trick you can use to set default command line parameters while also supporting overriding the default arguments with custom ones:
Step#1 In your dockerfile invoke your program like so:
ENV DEFAULT_ARGS "--some-default-flag=123 --foo --bar"
CMD ["/bin/bash", "-c", "./my-nifty-executable ${ARGS:-${DEFAULT_ARGS}}"]
Step#2 When can now invoke the docker-image like so:
# this will invoke it with DEFAULT_ARGS
docker run mydockerimage
# but this will invoke the docker image with custom arguments
docker run --env ARGS="--alternative-args --and-then-some=123" mydockerimage
You can also adjust this technique to do much more complex argument-evaluation however you see fit. Bash supports many kinds of one-line constructs to help you towards that goal.
Hope this technique helps some folks out there save a few hours of head-scratching.

Not sure if this helps but I have used it this way and it worked like a charm
CMD ["node", "--inspect=0.0.0.0:9229", "--max-old-space-size=256", "/home/api/index.js"]

I found this at docker-compose not setting environment variables with flask
docker-compose.yml
version: '2'
services:
app:
image: python:2.7
environment:
- BAR=FOO
volumes:
- ./app.py:/app.py
command: python app.py
app.py
import os
print(os.environ["BAR"])

Related

Docker: Running two services with one Dockerfile

I am working on telegram group for premium members in which I have two services, one is monitoring all the joinee and other one is monitoring if any member has expired premium plan so It would kick out that user from the channel. I am very very new to Docker and deployment things. So I am very confused that, to run two processes simultaneously with one Dockerfile. I have tried like this.
here is the file structure:
start.sh
#!/bin/bash
cd TelegramChannelMonitor
pm2 start services/kickNonPremium.js --name KICK_NONPREMIUM
pm2 start services/monitorTelegramJoinee.js --name MONITOR_JOINEE
Dockerfile
FROM node:12-alpine
WORKDIR ./TelegramChannelMonitor
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
ENTRYPOINT ["/start.sh"]
What should I do to achieve this?
A Docker container only runs one process. On the other hand, you can run arbitrarily many containers off of a single image, each with a different command. So the approach I'd take here is to build a single image; as you've shown it, except without the ENTRYPOINT line.
FROM node:12-alpine
# Note that the Dockerfile already puts us in the right directory
WORKDIR /TelegramChannelMonitor
...
# Important: no ENTRYPOINT
# (There can be a default CMD if you think one path is more likely)
Then when you want to run this application, run two containers, and in each, make the main container command run a different script.
docker build -t telegram-channel-monitor .
docker run -d -p 8080:8080 --name kick-non-premium \
telegram-channel-monitor \
node services/kickNonPremium.js
docker run -d -p 8081:8080 --name monitor-joined \
telegram-channel-monitor \
node services/monitorTelegramJoinee.js
You can have a similar setup using Docker Compose. Set all of the containers to build: ., but set a different command: for each.
(The reason to avoid ENTRYPOINT here is because the syntax to override the command gets very clumsy: you need --entrypoint node before the image name, but then the rest of the arguments after it. I've also used plain node instead of pm2 since a Docker container provides most of the functionality of a process supervisor; see also what is the point of using pm2 and docker together?.)
Try pm2 ecosystem for apps(i.e services) declaration and run pm2 in non-backrgound mode or pm2-runtime
https://pm2.keymetrics.io/docs/usage/application-declaration/
https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/

Run docker from powershell script, and passing `--env` variable if is set

I'm trying to port a bash script to powershell. In bash, I have:
docker run --env MYVARIABLE ...
If MYVARIABLE is set, then it will be passed to docker run, otherwise it will be omitted.
Is it possible to simulate the same behavior from powershell?
On my linux host I have in my env EDITOR=vim.
So If I run a docker instance with this variable, it will push it in the env:
docker run -it --rm --env EDITOR --env EDITOR_VAR_NOT_EXISTS debian:stretch-slim env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=2ff94a96ef77
TERM=xterm
EDITOR=vim
HOME=/root
I've tried the same on Windows, but it's not working.

Dockerized React App failed to bind to $PORT on Heroku

I'm trying to deploy a Dockerized React App to Heroku, but keep getting the
"R10: Failed to bind to $PORT error on Heroku"
.
The dockerized app runs perfectly fine when i docker run it locally.
My docker file looks like the following:
FROM node:10.15.3
RUN mkdir -p /app
WORKDIR /app
COPY . .
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --verbose
RUN npm install serve -g -silent
# start app
RUN npm run build
CMD ["serve", "-l", "tcp://0.0.0.0:${PORT}", "-s", "/app/build"]
I followed the online solution to change the "listening" port on serve to $PORT from Heroku. Now the application is served on Heroku's port according to logs, but still, get the
"Failed to bind to $PORT error"
.
Please help!
variable substitution does not happen in CMD that is why ${PORT} is consider as a text instead of consuming its value.
Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, CMD [ "echo", "$HOME" ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: CMD [ "sh", "-c", "echo $HOME" ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.
docker-cmd
Change CMD to
CMD ["sh", "-c", "serve -l tcp://0.0.0.0:${PORT} -s /app/build"]

Docker Container exits upon running with "sh -c"

I am trying to run a webserver (right now still locally) out of a docker container. I am currently going step by step to understand the different parts.
Dockerfile:
FROM node:12.2.0-alpine as build
ENV environment development
WORKDIR /app
COPY . /app
RUN cd /app/client && yarn && yarn build
RUN cd /app/server && yarn
EXPOSE 5000
CMD ["sh", "-c","NODE_ENV=${environment}", "node", "server/server.js"]
Explanation:
I have the "sh", "-c" part in the CMD command due to the fact that without it I was getting this error:
docker: Error response from daemon: OCI runtime create failed:
container_linux.go:346: starting container process caused "exec:
\"NODE_ENV=${environment}\": executable file not found in $PATH":
unknown.
Building the container:
Building the container works just fine with:
docker build -t auth_example .
It takes a little while since the build context is (even after excluding all the node_modules) roughly 37MB, but that's okay.
Running the container:
Running the container and the app inside works like a charm if I do:
MyZSH: docker run -it -p 5000:5000 auth_example /bin/sh
/app # NODE_ENV=development node server/server.js
However, when running the container via the CMD command like this:
MyZSH: docker run -p 5000:5000 auth_example
Nothing happens, no errors, no nothing. The logs are empty and a docker ps -a reveals that the container was exited right upon start. I did some googling and tried different combinations of -t -i -d but that didn't solve it either.
Can anybody shed some light on this or point me into the right direction?
The problem is you're passing three arguments to sh -c whereas you'd usually pass one (sh -c "... ... ...").
It's likely you don't need the sh -c invocation at all; use /usr/bin/env to alias that environment variable instead (or just directly pass in NODE_ENV instead of environment):
FROM node:12.2.0-alpine as build
ENV environment development
WORKDIR /app
COPY . /app
RUN cd /app/client && yarn && yarn build
RUN cd /app/server && yarn
EXPOSE 5000
CMD /usr/bin/env NODE_ENV=${environment} node server/server.js

How to open remote shell to node.js container under docker-compose (Alpine linux)

I have a docker-compose.yml configuration file with several containers and one of the containers is node.js docker instance.
By some reason the docker instance returns error during start. In the result it's not possible to connect to the node.js container and investigate issue.
What is the simplest way to connect to the broken node.js under Alpine linux?
Usually in my docker-compose.yml
I just replace the command or entrypoint by :
command: watch ps
It's a bit hackish, but that keeps the container up.
Alternatively, once the image has been built, you can run it using docker. But then you have to do what you did in your docker-compose.yml file in your command, like mount volumes and open ports manually.
FOR DOCKER-COMPOSE
In case if you use docker-compose the simplest way is to add the following command line into your docker-compose.yml file.
services:
api:
build: api/.
command: ["/bin/sh", "-c", "while sleep 3600; do :; done"]
depends_on:
- db
- redis
...
also it need to comment line by line from bottom up inside the Dockerfile for node.js until the container will be able to start.
After the node.js container will be able to start you can easy connect to your container via
docker exec -it [container] sh
FOR DOCKER
You can simply add at the end of Dockerfile the following line
CMD echo "^D for exit" && wc -
and comment line by line (from bottom up) above this line until the container will be able to start.
You can docker-compose run an alternate command. This requires no changes in your Dockerfile or docker-compose.yml. For example,
docker-compose run --rm web /bin/sh
This creates a new container which is configured identically to what is requested in the docker-compose.yml (with environment variables and mounted volumes), except that ports: aren't published by default. It is essentially identical to docker run with the same options, except it defaults to -i -t being on.
If your Dockerfile uses ENTRYPOINT instead of CMD to declare the main container command, you need the same --entrypoint option. For example, to get a listing of the files in the image's default working directory, you could
docker-compose run --rm --entrypoint /bin/ls web -l
(If your ENTRYPOINT is a wrapper script that ultimately exec "$#" you don't need this.)

Resources