Skip to main content

How To Deploy Any Server Using Systemctl (Node JS as Example)

ยท 4 min read

Image

Suppose you have VM in the cloud, if you dont have you can have it free here. You may wonder how to deploy your apps so that can be access online.

Here's how to deploy any server using systemctl with nodejs as example.

Common way to do it by using docker, but our approach right now is using systemctl. Systemctl is utility for controlling the systemd system and service manager. I always analogy with the process that run along with system in paralel.

Create Simple App

Let's start with create simple webservice using nodejs and express.

Install dependencies

  1. Connect via SSH

  2. Update package Linux

    sudo apt update
  3. Install Node.js

    sudo apt install nodejs

    Check with:

    $ node -v
    v8.10.0

    You may have different version

  4. Install NPM

    sudo apt install npm

Create Express App

Quick start create express app here with some modification.

  1. Install executable

    npm install -g express-generator@4
  2. Create app

    Create starting app

    express ~/foo && cd ~/foo
  3. Install dependencies

    npm install
  4. Start server

    Before we start, we have to change port server 3000 to 80.

    sudo pico bin/www

    Search for 3000 or go to line (CTRL + SHIFT + _) 15 and change into 80.

    Exit (CTRL + X).

    Run server

    sudo npm start

    IMPORTANT: make sure it on the port 80

Your application now is running, but after you close the SSH the application will vanish and you cant access it.

Now, we're moving to the next step. Stop the app, if it still running.

Create Service

Systemctl consist of many units system.

Check units with sudo systemctl list-units, bunch of unit will come up.

Here's the example:

ilhamsyahids@instance-1:~$ systemctl list-units
UNIT LOAD ACTIVE SUB DESCRIPTION
... ... ... ... ...
accounts-daemon.service loaded active running Accounts Service
apparmor.service loaded active exited AppArmor initialization
apport.service loaded active exited LSB: automatic crash report generation
atd.service loaded active running Deferred execution scheduler
blk-availability.service loaded active exited Availability of block devices
chrony.service loaded active running chrony, an NTP client/server
... ... ... ... ...

Create Unit

In order to use systemctl, you have to create the unit.

Suppose you are in the app directory (/home/$USER/foo)

Create file, let's called foo.service

touch foo.service

Open file then write

[Unit]
Description=Foo application

[Service]
User=<USER>
WorkingDirectory=/home/<USER>/foo
ExecStart=/usr/bin/npm start
Restart=on-failure

[Install]
WantedBy=multi-user.target

For example:

[Unit]
Description=Foo application

[Service]
User=ilhamsyahids
WorkingDirectory=/home/ilhamsyahids/foo
ExecStart=/usr/bin/npm start
Restart=on-failure

[Install]
WantedBy=multi-user.target

Will quickly deep dive in the end of article

Save and close.

Run Unit

  1. Move unit into systemd folder

    cp foo.service /etc/systemd/system
  2. Reload daemon

    systemctl daemon-reload
  3. Start unit

    systemctl start foo.service
  4. Check status unit

    systemctl status foo.service

    You'll find something like this:

    ilhamsyahids@instance-1:~$ systemctl status foo.service
    โ— foo.service - Foo application
    Loaded: loaded (/etc/systemd/system/foo.service; disabled; vendor preset: enabled)
    Active: active (running) since Fri 2021-12-03 11:28:45 UTC; 5h 18min ago
    Main PID: 5405 (npm)
    Tasks: 21 (limit: 1120)
    CGroup: /system.slice/foo.service
    โ”œโ”€5405 npm
    โ”œโ”€5451 sh -c node ./bin/www
    โ””โ”€5452 node ./bin/www

    Dec 03 11:29:05 instance-1 npm[5405]: GET / 304 787.911 ms - -
    Dec 03 11:29:05 instance-1 npm[5405]: GET /stylesheets/style.css 304 1.683 ms - -

Make sure your VM is open HTTP and HTTPS

Now your app will serve even when you are exit the SSH connection, your app will remain.

Deep Dive

Found article that describe structure in the unit here.

[Unit]
Description=Foo application

[Service]
User=ilhamsyahids
WorkingDirectory=/home/ilhamsyahids/foo
ExecStart=/usr/bin/npm start
Restart=on-failure

[Install]
WantedBy=multi-user.target

Unit contains three section:

  • [Unit]

    Information about the unit.

  • [Service]

    Information about "what will you do"

  • [Install]

    Information about where unit will served

Focus on "what will you do":

  • Property User optional but make as least privilege.

  • Instead of using cd to our app folder, simply using WorkingDirectory=<path-app-folder>

  • ExecStart the important property for execute the app. Do not reference with alias such as npm start but point into the binary exec /usr/bin/npm start

    Suppose you have any server you can change exec here. e.g. Ruby: /home/user/.rbenv/shims/ruby main.rb

  • Restart=on-failure restart the process when it crashed

  • Environment the env variable. e.g: Environment="ENV=production"

Next Step

  • Serve using reserve proxy NGINX or Caddy
  • Deploy many app with multiple port in single VM (port forwarding)

Reach Me

Any issue? Reach me at [email protected]