DEV Community

Tyler Smith
Tyler Smith

Posted on • Originally published at tinkerlog.dev

A complete guide to deploying Rails on an Ubuntu VirtualBox VM

This guide assumes you have a fresh host computer with VirtualBox installed and a Rails app on a public GitHub repo.

This guide will use Puma for the application server, nginx for the webserver, and will not use Capistrano.

Download Ubuntu Server

Download the Ubuntu Server image from the Canonical Ubuntu Server page. You may need to look for a section like 'Manual server installation' to find the download.

Configuring VirtualBox

Open VirtualBox then select the "New" icon using the application toolbar.

Enter an appropriate name for the VM, and keep the default machine folder.

Select Linux for Type and select Ubuntu (64 bit) for Version. This will not install the OS on your VM, but it will set up a VM with sensible defaults.

Next, select the default memory size of 1024 MB, then select the Create a virtual hard disk now option.

It will present you with 3 options:

  1. VDI: The native VirtualBox Image.
  2. VHD: Microsoft's Virtual Hard Drive format.
  3. VMDK: VMware's Virtual Machine Disk.

While each format has pros and cons, there is some amount of interoperability, and some amount of ability to convert between formats. Because of this, we will stick with the default VDI, then select Dynamically allocated size and keep the default 10.00 GB disk size.

The VM is now created.

Select the VM from the left-hand sidebar, then click "Settings" from the application toolbar.

Navigate to the Network tab in the left-hand sidebar. Go to the Advanced settings under Adapter 1, then click the Port Forwarding button.

Add the following configuration, changing the Host Port values to whatever you please:

Name Protocol Host IP Host Port Guest IP Guest Port
SSH TCP 127.0.0.1 2222 22
HTTP TCP 127.0.0.1 2223 80

Click the "Start" icon in the application toolbar. A pop up will appear asking you to select a startup disk. Use the file selector to select the Ubuntu Server image you downloaded earlier, then click the Start button.

Installing Ubuntu

Select your preferred language and keyboard. Keep the defaults for network connections, proxies, Ubuntu archive mirror, and storage configuration. The installer will prompt you to confirm that you understand that selecting the storage is a destructive action–confirm and proceed.

Choose your name, server name, username and password, then continue by selecting Done.

The installer will ask if you'd like to install OpenSSH server. Select Install OpenSSH server and continue. We won't import an SSH identity yet because we'll create ssh keys later in this guide. If you already have SSH keys, you can import them now using GitHub or Launchpad.

Next the installer will prompt you to install server Snaps. We will continue without installing any Snap packages.

The installer will finish installing software, then it will prompt you to restart.

Once Ubuntu has finished installing, select Reboot Now.

If you see the terminal hanging on CD unmounting errors, hit enter and the VM will proceed with the reboot.

After the VM reboots, you'll see a very busy screen. Hit enter and you should see the login prompt. Log in with the username and password that you created earlier.

Generating SSH keys and connecting to the server

We will be using the terminal from your host machine for the rest of this guide.

If you've never generated an SSH key on your current computer, run the following command in the host machine's terminal:

ssh-keygen -b 4096
Enter fullscreen mode Exit fullscreen mode

The -b 4096 command gives you a 4096-bit key instead of the default 3072-bit key, with larger keys being more secure.

When prompted by the command, confirm the default directory and set a long, hard-to-guess passcode.

Next, copy the SSH key to the server with the following command, replacing username with your username. The -p flag specifies the SSH port we set when configuring our VirtualBox:

ssh-copy-id username@localhost -p 2222
Enter fullscreen mode Exit fullscreen mode

Enter your password when prompted.

Now, we will SSH into the VM itself.

ssh username@localhost -p 2222
Enter fullscreen mode Exit fullscreen mode

Next, we want to disable password access to make it difficult for hackers to gain control over the server. Edit /etc/ssh/sshd_config using sudo vim /etc/ssh/sshd_config and find the line that says PasswordAuthentication yes and set it to PasswordAuthentication no.

Save and quit Vim, then type exit to break the SSH connection, then SSH into the server again to confirm everything worked.

Installing Nginx

First we will install Nginx. Ubuntu's official guide recommends using the following installation method:

sudo apt install nginx
Enter fullscreen mode Exit fullscreen mode

Enabling the Firewall

We'll use UFW (uncomplicated firewall), a simplified wrapper around IP Tables, to limit access to the server.

Run the following to see the apps that UFW can allow:

sudo ufw app list
Enter fullscreen mode Exit fullscreen mode

It should return the following:

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH
Enter fullscreen mode Exit fullscreen mode

Run the following commands:

sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

This will enable the firewall. If you did this wrong, the VM will boot you off of SSH and you'll have to open the VirtualBox to fix it.

Installing Node.js

We'll install Node.js with NodeSource's distributions. While NVM is also a powerful tool, the way it installs Node makes it difficult for system tasks to run node, whereas NodeSource installs Node as a standard package.

Navigate to the NodeSource Distributions GitHub and find the most recent LTS version of Node in the Debian section. LTS info can be found on the releases page of the Node website.

Run the Ubuntu install scripts:

# Using Ubuntu
curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs
Enter fullscreen mode Exit fullscreen mode

Installing Yarn

Now we'll install Yarn package manager.

Run the following commands:

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt install yarn
Enter fullscreen mode Exit fullscreen mode

Installing Database Dependencies

If you need a local instance of MySQL or Postgres, install them now. SQLite3 will work without the system package installed.

Installing rbenv, ruby-build, and rbenv-vars

We're eventually going to install Ruby with rbenv, so install rbenv.

sudo apt install rbenv
Enter fullscreen mode Exit fullscreen mode

Next, run rbenv init and follow the instructions by appending the following to your ~/.bashrc file:

eval "$(rbenv init -)"
Enter fullscreen mode Exit fullscreen mode

Reload the session by running exec bash. This will replace the current bash shell with a new bash shellS without having to log out and log in again.

Now we need to install Ruby. While rbenv has a built-in install command, don't let it trick you: you need the ruby-build package to get recent versions of Ruby.

Install ruby-build as a rbenv plugin:

mkdir -p "$(rbenv root)"/plugins
git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
Enter fullscreen mode Exit fullscreen mode

Next, download the rbenv-vars plugin that lets us set Ruby enviornment variables.

git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars
Enter fullscreen mode Exit fullscreen mode

Creating a project directory

Create a folder for the project inside of the /srv directory.

sudo mkdir /srv/http
Enter fullscreen mode Exit fullscreen mode

Next, create a webadmin group for the web application.

sudo groupadd webadmin
Enter fullscreen mode Exit fullscreen mode

Now, add the webadmin group to the current user:

sudo usermod -aG webadmin tyler
Enter fullscreen mode Exit fullscreen mode

To make the new group take effect without restarting the computer, run the following command (courtesy of Stack Overflow):

exec su -l $USER
Enter fullscreen mode Exit fullscreen mode

Next, change the group owner of the http directory to the webadmin group:

sudo chgrp webadmin /srv/http
Enter fullscreen mode Exit fullscreen mode

Change the permissions of the directory so that the webadmin group has write access:

sudo chmod g+w /srv/http
Enter fullscreen mode Exit fullscreen mode

Then change into the newly-created http directory:

cd /srv/http
Enter fullscreen mode Exit fullscreen mode

Cloning the Rails project

Inside the directory, clone your project using git clone. It should look similar to the following:

git clone  https://github.com/username/project.git
Enter fullscreen mode Exit fullscreen mode

Change into the cloned project directory.

Installing Ruby

Inside your project directory, run bin/rails. The console will display an error like the one below:

Warning: the running version of Bundler (2.1.2) is older than the version that created the lockfile (2.1.4). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.1.4`.
Your Ruby version is 2.7.0, but your Gemfile specified 2.7.2
Enter fullscreen mode Exit fullscreen mode

Use the error to find what version of Ruby to install, then install it with rbenv.

rbenv install 2.7.2
Enter fullscreen mode Exit fullscreen mode

This command will take a while to execute because it is compiling Ruby from source.

Once Ruby is compiled, run the rehash command then set the global Ruby version:

rbenv rehash
rbenv global 2.7.2
Enter fullscreen mode Exit fullscreen mode

Installing Gems

Next, install the missing Gems with Bundle:

bundle install
Enter fullscreen mode Exit fullscreen mode

Installing NPM Packages

Run yarn.

Setting the app environment variables

Create .rbenv-vars in the root of the project directory. Add the following:

RAILS_ENV=production
RAILS_SERVE_STATIC_FILES=true
Enter fullscreen mode Exit fullscreen mode

The second command will allow the Rails server to serve static files. This will simplify our nginx configuration, but it will have a negative impact on performance.

Precompiling assets

If you ran the app now, you'd have an error. That's because in production, we must precompile our app.

Run the following command in your project folder:

bin/rails assets:precompile
Enter fullscreen mode Exit fullscreen mode

Copying the master key

In your local copy of the site, copy the contents of config/master.key and place in your projects config folder on the VM.

Run migrations

If you already ran you migrations before you set your app to production, you'll need to run them again.

Run bin/rails db:migrate

After the migration, run any seeding scripts your app has.

Testing

Run your app with bin/rails server. In another ssh session to the VM, run curl localhost:3000. You should see your markup displayed in the console.

Creating a Puma systemd service

Next, we need to create a service to keep the Rails app alive. We'll use Puma for the application server and keep its default settings. Puma is more powerful than Passenger, and it's installed by default with Rails.

We'll use the Puma systemd configuration docs as a starting point.

Create /etc/systemd/system/puma.service and add the following, replacing the WorkingDirectory and User values with the appropriate settings from your server:

[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
WorkingDirectory=<YOUR_APP_PATH>
User=<A_NON_PRIVILEGED_USER>
Restart=always
ExecStart=/usr/bin/bash -lc 'eval "$(rbenv init -)"; bin/rails server'

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

The Type must be set to simple. The ExecStart feels like a hack, but it's the best I can come up with right now.

The commands listed below can be used to control the newly-created Puma service. These were copied from the Puma systemd configuration docs.

# After installing or making changes to puma.service
sudo systemctl daemon-reload

# Enable so it starts on boot
sudo systemctl enable puma.service

# Initial start up.
sudo systemctl start puma.service

# Check status
sudo systemctl status puma.service

# A normal restart. Warning: listeners sockets will be closed
# while a new puma process initializes.
sudo systemctl restart puma.service
Enter fullscreen mode Exit fullscreen mode

Configuring nginx

We're ready for the final step: configuring nginx.

Run sudo vim /etc/nginx/sites-enabled/default to edit the default nginx configuration.

Navigate to the location / block in the main server. Comment out try_files $uri $uri/ =404; and then add proxy_pass http://localhost:3000; on the next line.

It should look like this:

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                # try_files $uri $uri/ =404;
                proxy_pass http://localhost:3000;
        }
Enter fullscreen mode Exit fullscreen mode

Test your configuration with sudo nginx -t. If the configuration looks good, reload nginx with sudo systemctl reload nginx.

On your host system, navigate to http://localhost:2223 (we set up this port when we set up VirtualBox) and you should see your Rails app.

Discussion (0)