Skip to content

Instantly share code, notes, and snippets.

@joepie91
Last active November 12, 2024 22:47
Show Gist options
  • Save joepie91/73ce30dd258296bd24af23e9c5f761aa to your computer and use it in GitHub Desktop.
Save joepie91/73ce30dd258296bd24af23e9c5f761aa to your computer and use it in GitHub Desktop.
Running a Node.js application using nvm as a systemd service

Read this first!

Hi there! Since this post was originally written, nvm has gained some new tools, and some people have suggested alternative (and potentially better) approaches for modern systems. Make sure to have a look at the comments to this article, before following this guide!


The original article

Trickier than it seems.

1. Set up nvm

Let's assume that you've already created an unprivileged user named myapp. You should never run your Node.js applications as root!

Switch to the myapp user, and do the following:

  1. curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash (however, this will immediately run the nvm installer - you probably want to just download the install.sh manually, and inspect it before running it)
  2. Install the latest stable Node.js version: nvm install stable

2. Prepare your application

Your package.json must specify a start script, that describes what to execute for your application. For example:

...
"scripts": {
    "start": "node app.js"
},
...

3. Service file

Save this as /etc/systemd/system/my-application.service:

[Unit]
Description=My Application

[Service]
EnvironmentFile=-/etc/default/my-application
ExecStart=/home/myapp/start.sh
WorkingDirectory=/home/myapp/my-application-directory
LimitNOFILE=4096
IgnoreSIGPIPE=false
KillMode=process
User=myapp

[Install]
WantedBy=multi-user.target

You'll want to change the User, Description and ExecStart/WorkingDirectory paths to reflect your application setup.

4. Startup script

Next, save this as /home/myapp/start.sh (adjusting the username in both the path and the script if necessary):

#!/bin/bash
. /home/myapp/.nvm/nvm.sh
npm start

This script is necessary, because we can't load nvm via the service file directly.

Make sure to make it executable:

chmod +x /home/myapp/start.sh

5. Enable and start your service

Replace my-application with whatever you've named your service file after, running the following as root:

  1. systemctl enable my-application
  2. systemctl start my-application

To verify whether your application started successfully (don't forget to npm install your dependencies!), run:

systemctl status my-application

... which will show you the last few lines of its output, whether it's currently running, and any errors that might have occurred.

Done!

@antono
Copy link

antono commented Jul 6, 2016

You can avoid additional bash script by adding node path to systemd unit

Environment=PATH=/home/myapp/.nvm/versions/node/v6.0.0/bin

@joepie91
Copy link
Author

joepie91 commented Jul 18, 2016

@antono There are a few problems with that, though:

  1. Changing your version in nvm doesn't affect the service like it should.
  2. If nvm ever changes its storage layout, your service will stop working.
  3. Environment variables are not set; if something expects certain environment variables like the NODE_PATH to exist, it will break.

This is why I'm using a wrapper script - it ensures that the environment is set up exactly like it would have if you'd started the application yourself from a shell.

@notxt
Copy link

notxt commented Sep 30, 2016

⭐⭐⭐⭐

@JimtotheB
Copy link

KillMode=process looks like it will only kill the start script and not any of the PIDs started by it. This should probably be omitted, or set to KillMode=control-group if you are finding systemctl stop my.service isn't being stopped properly.

@bryanlarsen
Copy link

Much better option:

Environment=NODE_VERSION=7
ExecStart=/opt/nvm/nvm-exec yarn serve

@heikomat
Copy link

@bryanlarsen this is awesome, works perfectly, even with screen:

[Unit]
After=network-online.target

[Service]
Type=forking
User=pi
Environment=NODE_VERSION=7
ExecStart=/usr/bin/screen -dmL LOGFILE.log -S SCREEN-NAME /home/pi/.nvm/nvm-exec npm start --prefix /PATH-TO-YOUR-PROJECT

[Install]
WantedBy=multi-user.target

@Vindexus
Copy link

Vindexus commented Mar 3, 2019

What does ". /home/myapp/.nvm/nvm.sh" do?

@joepie91
Copy link
Author

joepie91 commented Jul 3, 2019

@Vindexus It executes the code in /home/myapp/.nvm/nvm.sh in the current context of the shell, without spawning a new one. If you were to do bash /home/myapp/.nvm/nvm.sh, it would instantiate a new shell and run it in there.

@Denkneb
Copy link

Denkneb commented Aug 22, 2019

KillMode=process looks like it will only kill the start script and not any of the PIDs started by it. This should probably be omitted, or set to KillMode=control-group if you are finding systemctl stop my.service isn't being stopped properly.

Great)

@donnlee
Copy link

donnlee commented Aug 28, 2022

Truly great recipe.
I removed the KillMode=process line (as mentioned above) and added line RestartSec=30

@kyeno
Copy link

kyeno commented Jan 2, 2023

Problem is this whole snippet will not allow you to run systemctl stop my.service

@kyeno
Copy link

kyeno commented Jan 2, 2023

This is what works for both starting and stopping.

[Service]
Environment=NODE_VERSION=17.3.0 NODE_ENV=production
WorkingDirectory=/root/project
ExecStart=/root/.nvm/nvm-exec /root/project/index.js
...

@fabricionaweb
Copy link

nvm-exec is the right answer, thanks to point it out

@crazynewidea
Copy link

crazynewidea commented Feb 8, 2024

For the nvm-exec method, in my case the Environment=NODE_VERSION... arguments did not work, and I received the following error:
No NODE_VERSION provided; no .nvmrc file found
I had to include a .nvmrc file in the project WorkingDirectory. The .nvmrc file is a text file that simply contains the version number that you want to run as such as "10.20.0".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment