CI Guide: GitHub Actions and Digital Ocean

Node.js Full-Stack Application Deployment on Digital Ocean

Marwan Zaarab
4 min readNov 11, 2023

The focus of this article will be on establishing an efficient GitHub CI pipeline that automates the deployment process. You’ll learn how to pull the latest changes from your main branch, rebuild your application — be it React, Node.js, or a combination of both — and manage the PM2 process on a DigitalOcean droplet.

Notably, this guide addresses the creation and usage of two SSH key pairs: one for general GitHub operations and another as a Deploy Key for secure deployment interactions. By adhering to best practices in SSH key management, this tutorial ensures a secure and functional setup, avoiding common pitfalls like unnecessary exposure of private keys.

Throughout the guide, we’ll employ the following representative details:

  • DigitalOcean Username (the one we’ll create): deployer
  • Your main sudo username on the droplet: other_sudo_user
  • Application name & root directory of app: my_app
  • Your droplet’s IP: DROPLET_IP

Step 1: Create a new user and grant sudo privileges

Step 2: Configure GitHub SSH Access

  • Switch to the deployer user: su deployer
  • Create two SSH Key Pairs:

Generate SSH Key Pair #1 (Personal Key): For general Git operations.

ssh-keygen -t rsa -b 4096 -C ""
  • Save to a unique path: /home/deployer/.ssh/app_key
  • If the .ssh directory doesn't exist: sudo mkdir /home/deployer/.ssh
  • Display and copy the generated public key: cat ~/.ssh/
  • Add to GitHub Account: Paste the public key here.

Generate SSH Key Pair #2 (Deploy Key): Specifically for deployment.

ssh-keygen -t rsa -b 4096 -C "deploy_key_my_app"
  • Save to a unique path: /home/deployer/.ssh/deploy_key_my_app
  • Set permissions: chmod 600 ~/.ssh/deploy_key_my_app
  • Display and copy the public key: cat ~/.ssh/
  • Add as Deploy Key: Navigate to your GitHub repo’s settings, select “Deploy keys”, click “Add Deploy Key”, and paste the public key.

Step 3. Append Public Keys to Authorized Keys

Add SSH Keys to Authorized Keys:

  1. Personal key:
    cat ~/.ssh/ >> ~/.ssh/authorized_keys
  2. Deploy Key:
    cat ~/.ssh/ >> ~/.ssh/authorized_keys

Set permissions

  • For authorized_keys: chmod 600 ~/.ssh/authorized_keys
  • For .ssh directory: chmod 700 /home/deployer/.ssh

This setup ensures:

  • Both personal and deploy keys enable SSH access to the server.
  • authorized_keys is secure and well-configured.
  • The deploy key’s private key remains safely stored.
  • The .ssh directory has optimal permissions for security.

Step 4: Set Up SSH for Secure Remote Access

Add GitHub’s RSA Host Key:

ssh-keyscan -t rsa >> ~/.ssh/known_hosts
  • This step helps your server recognize and trust GitHub for SSH connections.

Change Ownership of the SSH directory:

sudo chown deployer:deployer /home/deployer/.ssh

Enable Password-less PM2 Commands:

  • Edit the sudoers file: sudo visudo
  • Add: deployer ALL=(other_sudo_user) NOPASSWD: /path/to/pm2
  • Replace /path/to/pm2 with the actual path to PM2 on your droplet, and other_sudo_user with your primary sudo username.

Step 5: Change Ownership of the App Directory

Transfer Directory Ownership:

cd /home/other_sudo_user && sudo chown -R deployer: my_app

Create a Symbolic Link:

  • Purpose: To simplify access to your app directory directly from the deployer user's home directory.
  • Command: ln -s /home/other_sudo_user/my_app /home/deployer/my_app
  • This link allows you to refer to /home/deployer/my_app instead of the longer path /home/deployer/home/other_sudo_user/my_app, simplifying file management and access.

Manage PM2 Processes:

  • If a PM2 process is running under your old sudo user, stop it.
  • Start a new PM2 process under the deployer user. This aligns the process management with your new deployment workflow.

Step 6: Set Up the GitHub Repository Workflow

Define secrets in your repository’s settings:

  • Add secrets here
  • DO_SSH_KEY: SSH private key for the deploy key.
  • DO_HOST: Your droplet's IP.
  • DO_USERNAME: Username created (deployer).

Create a CI Workflow File:

  • Navigate to your repo, select Actions, and create a new workflow file.

Create a new workflow file. You can use the same one I use and tailor it for your specific use case:

Key Workflow Components:

  • on/push: Triggers on any push to the main branch. For manual triggers, use workflow_dispatch
  • strategy/matrix: Specify Node.js versions. Example: 18.x

Deploy to DigitalOcean: This step does the actual deployment work.

  • Sets up the SSH key and permissions.
  • Uses scp to transfer application code to the droplet.
  • Connects via SSH into the droplet to build UI and manage PM2.
  • Runs: cd ~/my_app/server && npm run build:ui && pm2 restart 0
  • Cleans up by removing the temporary private key file.

Note: npm run build:ui is a custom script in my app server’s package.json that handles several tasks: it goes to the client directory, builds the UI, transfers the build to the server, and then pm2 restart 0 restarts the PM2 process initiated by the deployer user.

Step 7: Commit and Push Changes

  • Finalize Your Setup: Commit and push your changes. Monitor the GitHub Actions tab to observe your CI pipeline.
  • Outcome: Each push to the main branch triggers an automated deployment to your DigitalOcean server, seamlessly updating your React/Node.js application.