Docker & PM2: String based CMD with environment variables - node.js

I'm currently using the shell-form of CMD in Docker for launching my node app:
CMD /usr/src/app/node_modules/.bin/trifid --config $TRIFID_CONFIG
The env-var TRIFID_CONFIGis set to a default in the Dockerfile:
ENV TRIFID_CONFIG config.customer.json
This makes it easy to pass another config file for dev-environments for example.
Now I try to switch this to PM2 for production. However it looks like all PM2 samples are using the "exec" form which from what I understood does not evaluate ENV-vars. I tried the shell-form with PM2:
CMD pm2-docker /usr/src/app/node_modules/trifid/server.js --config $TRIFID_CONFIG
But it looks like the variable is not evaluated like this, it fails back to default on execution.
What would be the proper way to handle this with PM2 inside a Docker image?

I had a discussion on Github and meanwhile figured it out:
CMD pm2-docker /usr/src/app/node_modules/.bin/trifid -- --config $TRIFID_CONFIG
So the trick is to use -- after the command and the rest will be passed as argument. If I use the shell form env-vars do seem to get evaluated properly.

Related

npm adds whitespace when setting env variable in package.json

I have a pre-written package.json file for an app which I need to modify. More specifically, I want to change the NODE_PORT environment variable through the package.json file and I'm working on a Windows machine.
In the package.json I have several scripts that I run through npm when I like to spin up an instance of the app.
For example:
set NODE_PORT=80&& set NODE_ENV=test&& pm2 install pm2-logrotate&& pm2 start app.js -i max -o ./logs/access.log -e ./logs/err.log --time --name Test
This script for example works fine.
However, when I'm trying to set the NODE_PORT variable to 8080 (that's the port I need) like so:
set NODE_PORT=8080&& set NODE_ENV=parallel_test&& pm2 install pm2-logrotate&& pm2 start app.js -i max -o ./logs/parallel_access.log -e ./logs/parallel_err.log --time --name Parallel_Test
a whitespace at the end of the variable gets added.
I verified this by printing out the number of chars of $process.env.NODE_PORT in the log file which prints 5. Moreover the login for the app via Google crashes as the redirect link of the app doesn't match with the one in the Google Cloud Platform. That is:
app: http://localhost:8080 /auth/check-google vs. Google Cloud Platform: http://localhost:8080/auth/check-google
Any idea why this is happening?
i have faced similar issue recently. Handled it with .trimEnd() while adding variables with dotenv. But I think using cross-env can solve your problems.
Most Windows command prompts will choke when you set environment
variables with NODE_ENV=production like that. (The exception is Bash
on Windows, which uses native Bash.) Similarly, there's a difference
in how windows and POSIX commands utilize environment variables. With
POSIX, you use: $ENV_VAR and on windows you use %ENV_VAR%.
Adding this inside your script: "cross-env NODE_PORT=8080 ..."

Conditionally setting ENV vars in Dockerfile based on build ARG

I'd like to conditionally set some ENV vars in my Dockerfile based on certain build ARGs. For one reason or another, the ENV var doesn't map directly to the actual build ARG i.e. I can NOT do the following:
ARG myvar
ENV MYVAR $MYVAR
It's complex enough a mapping that I can't just do, like, "prefix-${$MYVAR}", or whatever, either.
I'm aware of just using a shell command with EXPORT instead (and then having access to if statements), but exporting the environmental variable this way won't persist across containers the way I need to. If the only solution is to just repeatedly prefix the needed env for every RUN/CMD I need it for (and all the logic needed to get there), then I would accept that.
I'm also aware of this answer Conditional ENV in Dockerfile, which does have one solution where a (essentially) ternary is used to trigger a certain ENV value, but because my situation is more complex, I can't just use that given solution.
Is there a way to write "logic" inside Dockerfiles, while still having access to Docker commands like ENV?
Any help would be really appreciated!
PS.
This is a Dockerfile to build a Node image, so my last few steps look basically like
RUN npm run build
CMD ["npm", "run", "start"]
If I understand your question correctly, you want
if BUILD_VAR == v1:
set ENV MYVAR=aValue
else if YOUR_VAR == v2:
set ENV MYVAR=anotherValue
Solution:
ARG BUILD_VAR=v1
FROM scratch as build_v1
ONBUILD ENV MYVAR=aValue
FROM scratch as build_v2
ONBUILD ENV MYVAR=anotherValue
FROM build_${YOUR_VAR} AS final
...
RUN npm run build
CMD ["npm", "run", "start"]
The toplevel BUILD_VAR allows you to pass the condition with docker build --build-arg BUILD_VAR=v1 .
ONBUILD is used so the instruction ENV is only executed if the corresponding branch is selected.
I had a similar issue for setting proxy server on a container.
The solution I'm using is an entrypoint script, and another script for environment variables configuration. Using RUN, you assure the the configuration script runs on build, and ENTRYPOINT when you run the container.
The entrypoint script looks like:
#!/bin/bash
# Run any other commands needed
# Run the main container command
exec "$#"
And in the Dockerfile, configure:
#copy bash scripts
COPY configproxy.sh /root
COPY startup.sh .
RUN ["/bin/bash", "-c", ". /root/configproxy.sh"]
ENTRYPOINT ["./entrypoint.sh"]
CMD ["sh", "-c", "bash"]
Take a look here.

How to use pm2 with a nodejs app that uses readline for taking command line input?

I have a Node.js app that uses the node's native readline to be able to take command-line inputs.
When launching the app with pm2, the command-line input is unavailable.
Any ideas how to solve this issue? Other than using systemd and creating an init script myself?
Use pm2 to attach to your process and you will see readline, clearline and cursorTo working as expected.
First get your process id with:
$ pm2 id {your-process-name}
[ 7 ]
Let's say it's 7:
$ pm2 attach 7
if you check the pm2 website they clearly mention the following line: Advanced, production process manager for Node.js. So using it in this context is unnecessary as all pm2 does is start your 'node' process and allows you to manage it, the simple way is to use command line args while starting the process.
for example:
I myself use commander for this purpose. it manages all my command line arguments (u can see its usage). and with pm2 i use it like following:
pm2 start server.js --name production -- --env dev -p 3458
notice -- before --env, it is used to separate pm2 arguments from the arguments you want to supply to your process
p.s.
PM2 has more complex usage than this, in the terms of process management, i myself use it for production level deployment. If you want to take input from a user every time s/he starts your app, then you should stick with using node command only

Supervisor doesn't recognize environment variable in Nodejs

This is my supervisor setting for a NodeJS API, but the NODE_ENV variable doesn't get recognized. API works in Default mode right now. But I want it to run it in NODE_ENV development
[program:api]
command=/usr/bin/node /srv/apps/api/src/index.js
directory=/srv/apps/api
autostart=true
autorestart=true
startretries=3
stderr_logfile=/srv/logs/api/error.log
stdout_logfile=/srv/logs/api/out.log
user=api-user
environment= NODE_ENV="development"
Any ideas?
It seems that after typing supervisorctl reread you should also use supervisorctl update

Nodejs/Strongloop: working upstart config example

After update strongloop to v2.10 slc stops writing logs.
Also I couldn't make the app to start in production mode.
/etc/init/app.conf
#!upstart
description "StrongLoop app"
start on startup
stop on shutdown
env NODE_ENV=production
script
exec slc run /home/ubuntu/app/ \
-l /home/ubuntu/app/app.log \
-p /var/run/app.pid
end script
Can anybody check my upstart config or provide another working copy?
Are you were writing the pid to a file so that you can use it to send SIGUSR2 to the process to trigger log re-opening from logrotate?
Assuming you are using Upstart 1.4+ (Ubuntu 12.04 or newer), then you would be better off letting slc run log to its stdout and let Upstart take care of writing it to a file so that log rotation is done for you:
#!upstart
description "StrongLoop app"
start on startup
stop on shutdown
# assuming this is /etc/init/app.conf,
# stdout+stderr logged to: /var/log/upstart/app.log
console log
env NODE_ENV=production
exec /usr/local/bin/slc run --cluster=CPUs /home/ubuntu/app
The log rotation for "free" is nice, but the biggest benefit to this approach is Upstart can log errors that slc run reports even if they are a crash while trying to set up its internal logging, which makes debugging a lot easier.
Aside from what it means to your actual application, the only effect NODE_ENV has on slc run is to set the default number of cluster workers to the number of detected CPU cores, which literally translates to --cluster=CPUs.
Another problem I find is the node/npm path prefix not being in the $PATH as used by Upstart, so I normally put the full paths for executables in my Upstart jobs.
Service Installer
You could also try using strong-service-install, which is a module used by slc pm-install to install strong-pm as an OS service:
$ npm install -g strong-service-install
$ sudo sl-svc-install --name app --user ubuntu --cwd /home/ubuntu/app -- slc run --cluster=CPUs .
Note the spaces around the -- before slc run

Resources