PM2

reading time 5 mins

This guide will show you to use Doppler to supply app config and secrets to your PM2 managed Node.js applications.

You can also check out a complete working example at https://github.com/DopplerUniversity/pm2

Prerequisites

  • You're familiar with PM2
  • The Doppler CLI is installed
  • You're familiar with provising machines for deployment and CI/CD systems

Service Token

Accessing your secrets in production or CI/CD environment requires a Service Token to provide read-only access to a specific config and we'll cover how to provide this in the Deployment section.

Scripts

The easiest way to use Doppler with PM2 is by creating either a bash (recommended) or Node.js script that uses the Doppler CLI to inject secrets as environment variables into your process.

πŸ“˜

Using bash is recommended unless you're in an environment where bash doesn't exist (e.g. Windows without WSL).

Create a script that will call doppler run using the following naming conventions (PM2 only recognizes .sh, .js, and .py files):

  • bash: bin/doppler-run.sh
  • node: bin/doppler-run.js
doppler run -- npm start
import { spawn } from 'child_process'

const doppler = spawn('doppler', ['run', '--', 'npm', 'start'])
doppler.stdout.on('data', (data) => console.log(`${data}`))
doppler.stderr.on('data', (data) => console.error(`${data}`))

Now start your application using PM2:

pm2 start bin/doppler-run.sh
pm2 start bin/doppler-run.js

To stop or delete your application, use the name of the script without the file extension:

pm2 stop doppler-run
pm2 delete doppler-run

Cluster Mode

To run your PM2 application in cluster mode, the previously shown approach using scripts won't work as required because PM2 expects the supplied script to be a Node.js application.

Working around this is easy! You'll just need to add a small amount of new code to fetch secrets using the Doppler CLI from within your application.

Your ecosystem.config.js file will likely look similar to the below.

module.exports = {
  apps : [{
    name      : "app",
    script    : "./src/app.js", // Must be Node.js application code
    instances : 2,
    exec_mode : "cluster"
  }]
}

Add a file named doppler.js to your application that will be responsible for fetching secrets in JSON format from the CLI:

const execSync = require('child_process').execSync

function fetchSecrets() {
  try {
    return JSON.parse(
      execSync('doppler secrets download --no-file --format json')
    )
  } catch (error) {
    process.exit(1)
  }
}

module.exports = { fetchSecrets }

Then use this code in your application entry point file to fetch secrets prior to configuring and bootstrapping your application:

const doppler = require('./doppler')
const secrets = doppler.fetchSecrets()

// Configure and initialize your application

With all the pieces in place, you can now start your application in cluster mode by running:

pm2 start ecosystem.config.js

Docker

Using Doppler with PM2 in a Docker container simply requires the installation of bash and the Doppler CLI (which is likely already installed if using a non-Alpine-based Node image).

Below is a complete and production-ready example Dockerfile:

FROM node:lts-alpine

# NOTE: PM2 requires bash to be installed
RUN apk add bash

# Install Doppler CLI
RUN (curl -Ls --tlsv1.2 --proto "=https" --retry 3 https://cli.doppler.com/install.sh || wget -t 3 -qO- https://cli.doppler.com/install.sh) | sh -s -- --verify-signature

WORKDIR /usr/src/app
ENV PATH=$PATH:/usr/src/app/node_modules/.bin

COPY package.json package-lock.json ./
RUN npm clean-install --only=production --silent --no-audit && mv node_modules ../
COPY . .

# The `pm2-runtime` script is required when using Docker
CMD ["pm2-runtime", "bin/doppler-run.sh"]

Then to start the container, pass in a Doppler Service token as the DOPPLER_TOKEN environment variable:

# Presumes the DOPPLER_TOKEN environment variable is set in the hosting environment
docker run -e DOPPLER_TOKEN="$DOPPLER_TOKEN" your/docker-image

Deployment

Configuring the Doppler CLI in production requires a Doppler Service Token and there are two ways to supply this to your PM2 application.

Option 1: Preconfiguring the Doppler CLI (preferred)

Pre-configuring the Doppler CLI when provisioning the production machine is the most secure option and works by scoping the Doppler Service Token to the location of the application directory. This only needs to be performed once.

For example, if your app is checked out to /home/ubuntu/your-app then the command would be the following (replacing dp.st.prd.xxxx with your Service Token):

doppler configure set token dp.st.prd.xxxx --scope /home/ubuntu/your-app

# Security measure by removing the Service Token value from bash history
history -c

Option 2: ecosystem.config File

You can use an ecosystem.config file to store the service token but do not commit this to your repository.

Instead, this value should be dynamically inserted by a CI/CD deployment job. For example, this could be the run step in a GitHub Action:

# Uses bash heredoc syntax - https://linuxize.com/post/bash-heredoc/
cat << EOF > ecosystem.config.js
module.exports = {
  apps: [{
    script: 'bin/doppler-run.sh',
    env: {
      DOPPLER_TOKEN: '${{ secrets.DOPPLER_TOKEN }}'
    }
  }]
};
EOF

πŸ‘

Awesome Work!

Now you know how to use Doppler to supply secrets to your PM2 managed Node.js applications.