Baremetal deployment Cheatsheet

Hello everyone! Long time no show… anyways, I set up my first baremetal deployment today. Followed the guide but ran into quite a few problems, so I created a cheatsheet for my future-self, and i figured this might help others.
Please give it a read, and let me know if you get stuck somewhere. or if anything doesn’t make sense/

This is a cheatsheet, it assumes you kind of know what you are doing. Or at least have read the amazing guide in the docs first.

NEW KVM from Scratch Setup Guide

This guide outlines how to set up a new KVM from scratch. It’s based on the RedwoodJS documentation but includes additional steps and explanations. This guide was tested on a fresh install of Ubuntu 20.04.3 LTS.

After following this guide, you’ll have a fully functional redwood app running on a KVM with NGINX as the server to handle the web requests. The API will be server by the redwood server and managed by PM2.

Prerequisites

Ensure you have SSH credentials for the server. Once connected, follow the steps below.


1. Update Your Linux KVM

Before installing anything, it’s essential to ensure your system is up to date.

  1. Refresh the apt list and upgrade all installed packages:

    sudo apt update
    sudo apt upgrade
    
  2. Perform a full upgrade:

    sudo apt full-upgrade
    

    This command upgrades installed packages and removes obsolete ones.

  3. Remove unnecessary dependencies:

    sudo apt --purge autoremove
    

    This will clean up any unused packages that were automatically installed.

  4. Upgrade to a new release version (if available):

    sudo do-release-upgrade
    

    This ensures you’re on the latest release version of the OS.


2. Create a New Sudo User

  1. Create a new user:

    sudo adduser username
    
  2. Grant the user sudo privileges:

    sudo usermod -aG sudo username
    
  3. Exit the root user:

    exit
    
  4. Reconnect as the new user:

    ssh username@remote_host
    

3. Simplify SSH Login (if you have a key)

  1. Copy your SSH key to the remote server:
    On your local machine

    ssh-copy-id username@remote_host
    
  2. make sure the key is added to the ssh-agent:

    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/id_ed25519
    

    for MacOs, make sure you add it to your keychain

    ssh-add --apple-use-keychain ~/.ssh/id_ed25519
    
  3. Install Git:
    Back on the server

    sudo apt install git
    
  4. Set up agent forwarding for github.com:

Add the following lines to ~/.ssh/config:

Host github.com
  ForwardAgent yes
  1. Ensure SSH access to GitHub:

    ssh -T git@github.com
    

4. Install Node.js and NVM (Node Version Manager)

  1. Install Node.js:

    sudo apt install nodejs
    
  2. Install Curl (required for NVM installation):

    sudo apt install curl
    
  3. Install NVM:

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
    source ~/.bashrc
    
  4. Install and use the latest LTS version of Node.js:

    nvm install --lts
    nvm use --lts
    

5. Install Yarn

  1. Remove any existing Yarn installations:

    sudo apt remove yarn
    
  2. Use Corepack to manage Yarn:

    corepack enable
    
  3. Install Yarn 4:

    corepack prepare yarn@4.0.0 --activate
    

6. Handle SSH and Non-Interactive Sessions

Some distributions may have restrictions that interfere with non-interactive sessions. You may need to update .bashrc to handle this.

  1. Edit the .bashrc file:

    nano ~/.bashrc
    
  2. Comment out lines that prevent non-interactive behavior:

    # case $- in
    #   *i*) ;;
    #     *) return;;
    # esac
    

7. Increase Node Memory Limit

If your server has limited RAM (e.g., 512MB), you can increase Node.js’ memory limit to prevent build failures.

  1. Edit the .bashrc file:

    nano ~/.bashrc
    
  2. Add the following line to increase the Node memory limit:

    export NODE_OPTIONS="--max-old-space-size=460"
    
  3. Reload the .bashrc file:

    source ~/.bashrc
    

8. Install PM2 (Process Manager for Node.js)

  1. Install PM2 globally:

    npm install pm2 -g
    
  2. Configure PM2 to start on system boot:

    pm2 startup
    

    Follow the on-screen instructions to complete the setup (it will prompt you with a command to copy and paste).

  3. Give Node.js permission to bind to low-numbered ports (if required):

    sudo setcap CAP_NET_BIND_SERVICE=+eip $(which node)
    

9. Install and Configure NGINX

  1. Install NGINX:

    sudo apt install nginx
    
  2. Enable and start NGINX:

    sudo systemctl enable nginx
    sudo systemctl start nginx
    
  3. Check NGINX status:

    sudo systemctl status nginx
    

10. Set Up Firewall

  1. Install UFW (Uncomplicated Firewall):

    sudo apt install ufw
    
  2. Allow SSH connections:

    sudo ufw allow ssh
    
  3. Allow HTTP/HTTPS traffic for NGINX:

    sudo ufw allow 'Nginx Full'
    
  4. Enable the firewall:

    sudo ufw enable
    
  5. Check firewall status:

    sudo ufw status
    

11. Configure NGINX for Your Site

  1. Create the application directory:

    sudo mkdir /var/www/app
    sudo chown -R $USER:$USER /var/www/app
    sudo chmod -R 755 /var/www/app
    
  2. Add the .env file:

    sudo touch
    sudo nano /var/www/app/.env
    

    Add the following content:

    WEB_PORT=80
    API_PORT=8911
    
  3. Edit the NGINX configuration:

    sudo nano /etc/nginx/sites-available/default
    
  4. Configure NGINX for your app:
    Replace the default content with the following configuration:

    upstream redwood_server {
      server 127.0.0.1:8911 fail_timeout=0;
    }
    
    server {
      root /var/www/app/current/web/dist;
      server_name your_domain.com;
    
      gzip on;
      gzip_min_length 1000;
      gzip_types application/json text/css application/javascript application/x-javascript;
    
      sendfile on;
      keepalive_timeout 65;
    
      error_page 404 /404.html;
      error_page 500 /500.html;
    
      location / {
        try_files $uri /200.html =404;
      }
    
      location ^~ /static/ {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
      }
    
      location ~ /api(.*) {
        rewrite ^/api(.*) $1 break;
        proxy_pass http://redwood_server;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      }
    }
    

12. Deploy the Redwood App

  1. Set up the Baremetal deployment

    yarn rw setup deploy baremetal
    
  2. Update deploy.toml

    
     # See <https://redwoodjs.com/docs/deploy/baremetal> for more info
    
     [[production.servers]]
     host = "IP_ADDRESS"
     username = "e"
     privateKeyPath = "/users/user/.ssh/id_ed25519"
     agentForward = true
     sides = ["api","web"]
     packageManagerCommand = "yarn"
     monitorCommand = "pm2"
     path = "/var/www/app"
     processNames = ["api"]
     repo = "<git@github.com>:username/repo.git"
     branch = "main"
     keepReleases = 5
    
    
  3. Update redwood.toml

       [web]
    
       title = "App Title"
       port = "${WEB_PORT:8910}"
       apiUrl = "/api"
       includeEnvironmentVariables = [
         # Add any ENV vars that should be available to the web side to this array
         # See https://redwoodjs.com/docs/environment-variables#web
       ]
       [api]
         port = "${API_PORT:8911}"
       [browser]
         open = true
       [notifications]
         versionUpdates = ["latest"]
    
    
  4. Update ecosystem.config.js

  module.exports = {
    apps: [
      {
        name: 'api',
        cwd: 'current',
        script: 'node_modules/.bin/rw',
        args: 'serve api',
        instances: 'max',
        exec_mode: 'cluster',
        wait_ready: true,
        listen_timeout: 10000,
      },
    ],
  };

  1. Commit and push the changes

    git add .
    git commit -m "Update deployment configuration"
    git push origin main
    
  2. Deploy the app from your local machine:

    yarn rw deploy baremetal production --first-run
    
  3. On the server, restart the PM2 process:

    pm2 restart api
    

Say good bye to somebody elses server! Say hello to managing your own nightmares! :partying_face:

5 Likes

Awesome! @jacebenson had also gone through some of these steps recently to deploy to Vultr!
Anything different you had to do there, Jace?

Yes, @esteban_url I think yours is better than mine but I’ll update mine to include things I missed from yours. Deploying a RedwoodJS site using BareMetal (jace.pro)

1 Like

I added all my stuff and your stuff and did a test set up. Works great. I updated my blog post in case you wanted to compare.

2 Likes

Seems I’m still figuring out the starting the background jobs.

Glad it was helpful!

What do you mean starting the background jobs? PM2?

Getting the background jobs service to restart properly on upgrade, that and getting the api service to install and restart when not running on continued deployments.

If I understand correctly you are referring to this step, which is tricky: Baremetal deployment Cheatsheet

8. Install PM2 (Process Manager for Node.js)


2. Configure PM2 to start on system boot:

pm2 startup

Follow the on-screen instructions to complete the setup (it will prompt you with a command to copy and paste).

this is what it looks like when I run pm2 startup

~$ pm2 startup
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/home/e/.nvm/versions/node/v20.17.0/bin /home/e/.nvm/versions/node/v20.17.0/lib/node_modules/pm2/bin/pm2 startup systemd -u e --hp /home/e

you have to copy that command into your terminal and run it, this is that will restart the service automatically. in my case the command is:

DO NOT COPY IT FROM HERE
sudo env PATH=$PATH:/home/e/.nvm/versions/node/v20.17.0/bin /home/e/.nvm/versions/node/v20.17.0/lib/node_modules/pm2/bin/pm2 startup systemd -u e --hp /home/e
DO NOT COPY IT FROM HERE

Another thing that might help, is running the deployment’s first run again

yarn rw deploy baremetal production --first-run

hope this helps, but if it doesn’t we can keep trying to figure it out.

:v:

1 Like

I’m talking about these background jobs.
Background Jobs | RedwoodJS Docs

ohhh, I haven’t used those yet, but i imagine that you could need to add as another app on pm2. that way pm2 handles it the same way as your api/web sides(?)

I HAVENT TRIED THIS

// ecosystem.config.js
  module.exports = {
    apps: [
      {
        name: 'api',
        cwd: 'current',
        script: 'node_modules/.bin/rw',
        args: 'serve api',
        instances: 'max',
        exec_mode: 'cluster',
        wait_ready: true,
        listen_timeout: 10000,
      },
      {
        name: 'bg-jobs',
        cwd: 'current',
        script: 'node_modules/.bin/rw',
        args: 'jobs start',
        instances: 'max',
        exec_mode: 'cluster',
        wait_ready: true,
        listen_timeout: 10000,
      },
    ],
  };
1 Like

I tried this along witha few other attempts. so far its a manual step to start the background job after first deploy, then a step to restart the worker.

If I find some better way I’ll comment here.