0
votes

I have a Node app which consists of three separate Node servers, each run by pm2 start. I use concurrently to run the three servers, as a start-all script in package.json:

"scripts": {
    ...
    "start-all": "concurrently \" pm2 start ./dist/foo.js \" \"pm2 start ./dist/bar.js \" \"pm2 start ./dist/baz.js\"",
    "stop-all": "pm2 stop all",
    "reload-all": "pm2 reload all",
...
}

This all runs fine when running from the command line on localhost, but when I run it as a docker-compose command - or as a RUN command in my Dockerfile - only one of the server scripts (a random one each time I try it!) will launch, but then immediately exit. In my --verbose docker-compose output I can see the pm2 panel (listing name, version, mode, pid, etc.), but then this error message:

pm2 start ./dist/foo.js exited with code 0.

N.B: This is all with Docker running locally (on a Mac Mini with 16GB of RAM), not on a remote server.

If I docker exec -it <container_name> /bin/bash into the container and the run npm run start-all manually from the top level of the src directory (which I COPY over in my Dockerfile) everything works. Here is my Dockerfile:

FROM node:latest

# Create the workdir
RUN mkdir /myapp
WORKDIR /myapp

# Install packages
COPY package*.json ./
RUN npm install

# Install pm2 and concurrently globally.
RUN npm install -g pm2
RUN npm install -g concurrently

# Copy source code to the container
COPY . ./

In my docker-compose file I simply list npm run start-all as a command for the Node service. But it makes no difference if I add it to the Dockerfile like this:

RUN npm run start-all

What could possibly be going on? The pm2 logs show report nothing other than that the app has started.

2

2 Answers

1
votes

the first reason is pm2 start app.js start the application in background so that is why your container stops as soon as it runs pm2 start.

You need to start an application with pm2_runtime, it starts an application in the foreground. also you do not need concurrently, pm2 process.yml will do this job.

Docker Integration

Using Containers? We got your back. Start today using pm2-runtime, a perfect companion to get the most out of Node.js in production environment.

The goal of pm2-runtime is to wrap your applications into a proper Node.js production environment. It solves major issues when running Node.js applications inside a container like:

Second Process Fallback for High Application Reliability Process Flow Control Automatic Application Monitoring to keep it always sane and high performing Automatic Source Map Discovery and Resolving Support

docker-pm2-nodejs

The second important thing, you should put all your application in pm2 config file, as docker can only run the process from CMD.

Ecosystem File

PM2 empowers your process management workflow. It allows you to fine-tune the behavior, options, environment variables, logs files of each application via a process file. It’s particularly useful for micro-service based applications.

pm2 config application-declaration Create file process.yml

apps:
  - script   : ./dist/bar.js
    name     : 'bar'
  - script : ./dist/foo.js
    name   : 'worker'
    env    :
      NODE_ENV: development

then add CMD in Dockerfile

CMD ["pm2-runtime", "process.yml"]

remove command from docker-compose.

0
votes

Docker and pm2 provide overlapping functionality: both have the ability to restart processes and manage logs, for example. In Docker it's generally considered a best practice to only run one process inside a container, and if you do that, you don't necessarily need pm2. what is the point of using pm2 and docker together? discusses this in more detail.

When you run your image you can specify the command to run, and you can start multiple containers off of the same image. Given the Dockerfile you show initially you can launch these as

docker run --name foo myimage node ./dist/foo.js
docker run --name bar myimage node ./dist/bar.js
docker run --name baz myimage node ./dist/baz.js

This will let you do things like restart only one of the containers when its code changes while leaving the rest untouched.

You hint at Docker Compose; its command: directive sets the same property.

version: '3'
services:
  foo:
    build: .
    command: node ./dist/foo.js
  bar:
    build: .
    command: node ./dist/bar.js
  baz:
    build: .
    command: node ./dist/baz.js