Skip to content

Instantly share code, notes, and snippets.

@s-lyn
Created September 15, 2017 10:27
Show Gist options
  • Save s-lyn/4b8f813d9e9860ca8497f8042da4d86a to your computer and use it in GitHub Desktop.
Save s-lyn/4b8f813d9e9860ca8497f8042da4d86a to your computer and use it in GitHub Desktop.
Deploy nodejs app with gitlab.com and pm2

Deploy nodejs app with gitlab.com and pm2

This manual is about setting up an automatic deploy workflow using nodejs, PM2, nginx and GitLab CI. It is tested on:

  • Target server: Ubuntu 16.04 x64. This is suitable for Ubuntu 14.x.
  • Windows 10 on my PC to work.

I use Alpine Linux in Docker container (gitlab) to speed up deployment.

What do we need:

  • Account on gitlab.com
  • New virtual server with Ubuntu 16.04 x64 (or 14.x) to run application (i will call it the "target server")

Configure target server

1. Create new sudo-user

Login with SSH user "root " and run:

adduser ubuntu
usermod -aG sudo ubuntu

To check sudo access run:

su ubuntu
sudo ls -la /root

2. Install nodejs and npm

You can find official instruction here.

For Ubuntu 16.04 run:

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

To check an installation run:

node -v
npm -v

3. Install process manager pm2

PM2 is a beautiful production process manager for nodejs. It will observe, log and automatically restart your application if it fall. Run now:

sudo npm install -g pm2@latest

To enable auto start pm2 on reboot run:

pm2 startup

then (!important) follow the instructions on your screen (run displayed command).

4. Install git

sudo apt-get install git -y

If you haven't deploy git keys yet, you should run:

ssh-keygen -t rsa -b 4096 -C "[email protected]"

This command will generate private (/home/ubuntu/.ssh/id_rsa) and public (/home/ubuntu/.ssh/id_rsa.pub) key. Print public key:

cat /home/ubuntu/.ssh/id_rsa.pub

copy it clipboard and paste to gitlab (Repo settings / Tab "Repository" / Deploy Keys).

Check ssh access to repository:

On the question "The authenticity of host...?" answer "yes". If all is okay, you should see string like "Welcome to GitLab, yourUsername!".

5. Generate SSH keys for current server

Now we should generate SSH keys to access current server without password. Run next command again, but set file path to /home/ubuntu/access:

ssh-keygen -t rsa -b 4096 -C "[email protected]"

This command will generate private (/home/ubuntu/access) and public (/home/ubuntu/access.pub) key. Move new generated key to authorized_keys:

cat /home/ubuntu/access.pub >> ~/.ssh/authorized_keys

You must copy and save private key on your computer. To print this key on screen use:

cat ~/access

6. Install nginx

sudo apt-get install nginx -y
sudo rm /etc/nginx/sites-enabled/default

Open nginx config:

sudo nano /etc/nginx/sites-available/app

and replace it with:

server {
  listen 80;
  server_name app;
  location / {
    proxy_set_header  X-Real-IP  		$remote_addr;
	proxy_set_header  X-Forwarded-For 	$proxy_add_x_forwarded_for;
    proxy_set_header  Host       		$http_host;
	proxy_set_header  X-NginX-Proxy 	true;
    proxy_pass        http://127.0.0.1:3000;
	proxy_redirect off;
    proxy_buffering off;
  }
}

Then run:

sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
sudo systemctl restart nginx
# Note: for Ubuntu 14.x run instead: sudo service nginx restart

And check nginx status (it should be "active"):

sudo systemctl status nginx

Configure deployment with Gitlab CI

1. Create file ecosystem.config.jsin root directory of your project:

// Target server hostname or IP address
const TARGET_SERVER_HOST = process.env.TARGET_SERVER_HOST ? process.env.TARGET_SERVER_HOST.trim() : '';
// Target server username
const TARGET_SERVER_USER = process.env.TARGET_SERVER_USER ? process.env.TARGET_SERVER_USER.trim() : '';
// Target server application path
const TARGET_SERVER_APP_PATH = `/home/${TARGET_SERVER_USER}/app`;
// Your repository
const REPO = '[email protected]:yourUsername/test-server.git';

module.exports = {
  /**
   * Application configuration section
   * http://pm2.keymetrics.io/docs/usage/application-declaration/
   */
  apps: [
    {
      name: 'testApp',
      script: 'index.js',
      env: {
        NODE_ENV: 'development'
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000
      }
    }
  ],

  /**
   * Deployment section
   * http://pm2.keymetrics.io/docs/usage/deployment/
   */
  deploy: {
    production: {
      user: TARGET_SERVER_USER,
      host: TARGET_SERVER_HOST,
      ref: 'origin/master',
      repo: REPO,
      ssh_options: 'StrictHostKeyChecking=no',
      path: TARGET_SERVER_APP_PATH,
      'post-deploy': 'npm install --production'
        + ' && pm2 startOrRestart ecosystem.config.js --env=production'
        + ' && pm2 save'
    }
  }
};

2. Add secret variables in gitlab:

Go to gitlab.com -> Your project -> "Settings" -> "CI/CD" -> "Secret variables". Add some variables:

Variable Description
TARGET_SERVER_HOST Target server host like 127.0.0.1 or your.host.com
TARGET_SERVER_USER SSH username for login. Example ubuntu
TARGET_SERVER_SECRET_KEY_BASE64 Base64 encoded private RSA key to login target server. Make it protected

3. Create file .gitlab-ci.yml in root directory of project:

image: keymetrics/pm2:6

stages:
  - deploy

deploy_prod:
  stage: deploy
  script:
    - echo "====== Deploy to production server ======"
    - apk update && apk upgrade
    - apk add git openssh bash
    # Add target server`s secret key
    - mkdir ~/.ssh
    - echo $TARGET_SERVER_SECRET_KEY_BASE64 | base64 -d > ~/.ssh/id_rsa
    - chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
    - echo "Test ssh connection"
    - ssh -o StrictHostKeyChecking=no -T "ubuntu@$TARGET_SERVER_HOST"
    # Delploy
    - echo "Setup tagget server directories"
    - pm2 deploy ecosystem.config.js production setup 2>&1 || true
    - echo "make deploy"
    - pm2 deploy ecosystem.config.js production
  environment:
    name: deploying
  only:
  - master

If all is okay, your project will be automatically deployed every push and merge to master branch.

@ArnaudWurmel
Copy link

You probably should replace "ubuntu" by $TARGET_SERVER_USER in order to respect configuration files.

Anyway, thank you for the tutorial !

@mbaev
Copy link

mbaev commented Apr 26, 2018

Just typo "# Delploy" in third section.

@Phoscur
Copy link

Phoscur commented May 25, 2018

.gitlab-ci.yml:
- echo "Test ssh connection"
- ssh -o StrictHostKeyChecking=no -T "ubuntu@$TARGET_SERVER_HOST"
use gitlab var instead of hardcoded ubuntu user:
- ssh -o StrictHostKeyChecking=no -T "$TARGET_SERVER_USER@$TARGET_SERVER_HOST"

also what might trip someone up, is that you save the privat key base64 encoded - add another
cat id_rsa | base64 (to copy from console, right?).

Thank you for the recipe!

@notiles
Copy link

notiles commented Jun 15, 2018

Hello all,

Do we need to add a runner on the remote server ?

@alex-knyazev
Copy link

Very great thanks!!!!
And very thanks for @Phoscur for note about command to know base64 key

@tzjoke
Copy link

tzjoke commented Sep 5, 2018

Thank you.
But why generate ssh key twice? Is it necessary?:frowning:

@polizinha
Copy link

Guys... I'm getting

$ echo ${TARGET_SERVER_SECRET_KEY_BASE64} | base64 -d > ~/.ssh/id_rsa
base64: truncated base64 input

do you know why?

@Phoscur
Copy link

Phoscur commented Dec 18, 2018

Thank you.
But why generate ssh key twice? Is it necessary?

One is to access gitlab from the server to pull the newest source code (deploy key) and the other is for gitlab (base64 encoded env variable) to access your server via pm2 from a runner to initiate the pull.

It's best to avoid reusing any ssh-keys. But gitlab provides some reusability for deploy keys.

@volodalexey
Copy link

I thought that I do not need base64 encode/decode.
But I found out that in User > repo > CI / CD Settings under Variables you specify TARGET_SERVER_SECRET_KEY variable and If you will specify plain private key:

-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----

In this case gitlab web-site (because textarea element keep line endings) will truncate line endings and private key inside docker container will be corrupted!
You will get error in pipeline:

Load key "/root/.ssh/...privateKey...": invalid format
user@host: Permission denied (publickey).

That is why you need to encode private key before copy this key/content into GitLab variable:

$ cat ~/.ssh/...privateKey... | base64
# copy generated base64 string and paste into GitLab TARGET_SERVER_SECRET_KEY_BASE64 variable

Then you will get what you want at this line in .gitlab-ci.yml:

echo $TARGET_SERVER_SECRET_KEY_BASE64 | base64 -d > ~/.ssh/...privateKey...

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