Getting Started With Ruby (and Ruby on Rails) on OCI

February 15, 2022 | 13 minute read
Text Size 100%:

I like Ruby. I’ve had the pleasure of using it in a variety of applications and absolutely love how adaptable it is. Its object-oriented goodness is terrific — the ability to quickly and easily monkey-patch the functionality that I need at the time I need it is both a blessing and a curse. A lot of flexibility (some — not me —may argue too much) is what likely dissuades some would-be adopters, but for the rest of us, it’s great.

With that being said, Ruby isn’t as popular as it used to be. Python’s come along and stolen a lot of the limelight (at least as far as high-level runtime languages go). Particularly with the inclusion of object-oriented support in Python 3, Python is a good language. Regardless, I still like Ruby. And to be clear, it’s not that I’m a one-trick pony — I have programmed in C, C++, ASM, PHP, Python and many other languages. Still, Ruby holds a special place in my heart.

This article was born from a desire to help make it easier to use Ruby on OCI. It’s a really basic topic that some users might not need/want, but that many of us will likely find a useful reference. Without further ado, let’s dive into how to bootstrap an OCI instance for Ruby (and Ruby on Rails).

Um… this is simple. Why this article?

Yeah, I know. That was my initial reaction. There are a few variables that make this particular scenario a bit less straightforward than my initial reaction:

· aarch64

· rbenv

aarch64 architecture shouldn’t be an earth-shattering platform. After all, the plethora of Raspberry Pi (and other embedded/IoT) devices alone (not to mention the resurgence of RISC-based platforms in the enterprise) means that aarch64 isn’t the strange neighbor it might have been years ago. Sadly not all developers have caught up with the times, so there will occasionally be libraries and/or tools that just don’t work right (read: don’t install at all) on aarch64. This will change. It’s inevitable. Time and technology marches relentlessly onward, so it’s just a matter of time. Regardless, I’m super excited about it and am doing my best to be as polyglot as possible (utilizing AMD64 and aarch64) in my solutions, usually defaulting to aarch64 anymore.

rbenv is really one of the biggest factors here, as Ruby is available via dnf install ruby:

[opc@ruby ~]$ dnf info ruby
Oracle Linux 8 BaseOS Latest (aarch64)                                                   77 kB/s | 3.6 kB     00:00
Oracle Linux 8 BaseOS Latest (aarch64)                                                   44 MB/s |  38 MB     00:00
Oracle Linux 8 Application Stream (aarch64)                                             103 kB/s | 3.9 kB     00:00
Oracle Linux 8 Application Stream (aarch64)                                              46 MB/s |  27 MB     00:00
Oracle Linux 8 Addons (aarch64)                                                          69 kB/s | 3.0 kB     00:00
Available Packages
Name         : ruby
Version      : 2.5.9
Release      : 107.module+el8.4.0+20203+c00aa653
Architecture : aarch64
Size         : 87 k
Source       : ruby-2.5.9-107.module+el8.4.0+20203+c00aa653.src.rpm
Repository   : ol8_appstream
Summary      : An interpreter of object-oriented scripting language
URL          : http://ruby-lang.org/
License      : (Ruby or BSD) and Public Domain and MIT and CC0 and zlib and UCD
Description  : Ruby is the interpreted scripting language for quick and easy
             : object-oriented programming.  It has many features to process text
             : files and to do system management tasks (as in Perl).  It is simple,
             : straight-forward, and extensible.[opc@ruby ~]$

 

Most any serious Ruby development requires control over the Ruby runtime (not to mention Rails and any other gems that might be used). This is where rbenv shines. Yeah, this article could’ve been circumvented with sudo dnf install ruby, but that would’ve installed the specific version available in the yum repo at that moment… which is not what I want. Below in this tutorial, you’ll notice I’ve chosen to install 3.0.1, whereas the yum repo version (above) is 2.5.9. Maybe it’s not the end of the world for you, but I wanted something with a bit more control. I suspect many other Ruby developers out there will want this level of control too.

A bit about OCI

Before continuing, it’s worth pausing for a moment to set the context for where this will take place: Oracle Cloud Infrastructure (OCI). If you don’t have an OCI account yet, you really should get one!

Why should you have an OCI account?!

I’m glad you asked. With an OCI Always Free account, you can use compute, storage, DB and many other OCI cloud services for free. Forever. Yeah… that’s right. Forever! Makes sense as to why this is where we’ll be working, right?! Sign-up for an account here. For a nice follow-the-yellow-brick-road experience, check out the great how-to my esteemed colleague, Chris, wrote.

Getting started

Start by creating and logging into your OCI Compute instance. If you’re not sure how to do this, Chris happened to write another tutorial for this — now would be a good time to go through it. I’ve chosen to use an A1-based shape (VM.Standard.A1.Flex) running the latest Oracle Linux 8 image available at the time. I chose to use an arm CPU for my instance, though you could use an amd64-based CPU just as well. Before doing anything, let’s upgrade the system:

[opc@ruby ~]$ sudo dnf update -y && sudo dnf upgrade -y

I’ve started from scratch — a fresh, brand-new instance. Now let the fun begin! There are a couple of options for installing rbenv, but the method I’m going with is to use the rbenv installation script. Let’s do that now:

[opc@ruby ~]$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash[opc@ruby ~]$ echo 'PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
[opc@ruby ~]$ source ~/.bashrc
[opc@ruby ~]$ echo 'eval "$(rbenv init - bash)"' >> ~/.bash_profile
[opc@ruby ~]$ source ~/.bash_profile

Yay! We’ve got rbenv setup now. We need to install development tools and a bunch of other packages (libraries), so that Ruby can be built and installed locally on our machine (see this page for more info):

[opc@ruby ~]$ sudo dnf config-manager --set-enabled ol8_codeready_builder
[opc@ruby ~]$ sudo dnf groupinstall -y 'Development Tools'
[opc@ruby ~]$ sudo dnf install -y openssl-devel libffi-devel readline-devel gdbm-devel ncurses-devel libyaml-devel

It’s worth mentioning that the libyaml-devel package is in the ol8_codeready_builder yum repo (see this page for more info), which is why this was enabled. The above commands will install quite a few packages, so hang tight while it does its thing. Now that we can build Ruby runtimes, let’s install our first Ruby runtime.

Installing Ruby using rbenv

Notice how there’s no Ruby runtime installed (yet) on the system:

[opc@ruby ~]$ ruby -v
-bash: ruby: command not found
[opc@ruby ~]$

Let’s fix that. rbenv allows for setting a global (system-wide, default) Ruby runtime as well as the option of installing a specific Ruby runtime for a given directory (think project/application). For our needs, let’s just setup a single, global runtime. First you’ll want to determine the latest runtime versions available:

[opc@ruby ~]$ rbenv install -l
2.6.9
2.7.5
3.0.3
3.1.0
jruby-9.3.3.0
mruby-3.0.0
rbx-5.0
truffleruby-22.0.0.2
truffleruby+graalvm-22.0.0.2Only latest stable releases for each Ruby implementation are shown.
Use 'rbenv install --list-all / -L' to show all local versions.
[opc@ruby ~]$

Those are what’s available as of when I’m writing this — you’ll likely see different output when you run this on your machine. For me, I’m going to install 3.1.0 (you can go with whatever the latest is, to your liking).

WARNING: Versions are constantly changing. Choose the right version for your environment (which will likely be different than the versions shown in this article).

NOTE: dtrace

Before continuing… if you just try running rbenv install you’ll likely run into a snag that I hit, where dtrace is failing:

[opc@ruby ~]$ rbenv install 3.1.0
Installing ruby-3.1.0...BUILD FAILED (Oracle Linux Server 8.5 using ruby-build 20220125)Inspect or clean up the working tree at /tmp/ruby-build.20220126152117.391237.wsPH1J
Results logged to /tmp/ruby-build.20220126152117.391237.logLast 10 log lines:
compiling vm_backtrace.c
compiling vm_dump.c
compiling vm_sync.c
compiling vm_trace.c
compiling yjit.c
assembling coroutine/arm64/Context.S
processing probes in object files
dtrace: failed to link script ./probes.d: an error was encountered while processing array.o
DTrace 2.0.0 [Pre-Release with limited functionality]
make: *** [Makefile:469: probes.o] Error 1
[opc@ruby ~]$

Doing a quick search on the ‘net, I found this article that said to disable dtrace.

Moving forward…

With the obligatory contextual note out of the way, let’s press onward with the install! You’ll notice that’s why I’m setting the RUBY_CONFIGURE_OPTS environment variable, which passes the — disable-dtrace argument when running ./configure.

[opc@ruby ~]$ RUBY_CONFIGURE_OPTS="--disable-dtrace" rbenv install -v 3.1.0

Now rbenv needs to set this up as the global Ruby runtime:

[opc@ruby ~]$ rbenv global 3.1.0

Now we have a working, functioning Ruby runtime! Check it out:

[opc@ruby ~]$ ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [aarch64-linux]
[opc@ruby ~]$

Yay! Let’s refactor this just a bit so we can have a shorter rbenv command (setting the RUBY_CONFIGURE_OPTS variable by default for the shell):

[opc@ruby ~]$ echo 'export RUBY_CONFIGURE_OPTS="--disable-dtrace"' >> ~/.bashrc
[opc@ruby ~]$ source ~/.bashrc

Now we can run rbenv with shorter commands:

[opc@ruby ~]$ rbenv install 2.7.5

Voila! It worked. Because we’ve set the “disable dtrace” flag as an environment variable, we can merrily go along our way ignoring this altogether now (it’s there, present all the time). That was a bit of a journey, but not crazy monumental. Still, it’s worthwhile showing the “easy path” to get going quickly with Ruby development using Oracle Linux on OCI.

Installing Rails

Ruby is super awesome… but if you’re doing a web app, you’ll likely want to use the Rails (aka Ruby on Rails, abbreviated RoR) framework. With Ruby installed, this is actually really easy! Following the Rails installation directions, here’s what’s needed:

[opc@ruby ~]$ sudo dnf install -y nodejs

Yarn needs to be installed (following the install instructions):

[opc@ruby ~]$ sudo npm i -g corepack

Let’s install some packages and libraries that will be needed by some of the gems Rails uses:

[opc@ruby ~]$ sudo dnf install sqlite-devel
[opc@ruby ~]$ bundle config set force_ruby_platform true

If you don’t provide the second command, nokogiri installation will fail:

...
ERROR: It looks like you're trying to use Nokogiri as a precompiled native gem on a system with glibc < 2.17:/lib64/libm.so.6: version `GLIBC_2.29' not found (required by /home/opc/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/nokogiri-1.13.1-aarch64-linux/lib/nokogiri/3.1/nokogiri.so) - /home/opc/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/nokogiri-1.13.1-aarch64-linux/lib/nokogiri/3.1/nokogiri.soIf that's the case, then please install Nokogiri via the `ruby` platform gem:
      gem install nokogiri --platform=ruby
  or:
      bundle config set force_ruby_platform truePlease visit https://nokogiri.org/tutorials/installing_nokogiri.html for more help.
...

And it looks like we have glibc 2.28:

[opc@ruby ~]$ ldd --version
ldd (GNU libc) 2.28
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
[opc@ruby ~]$

So, we follow the directions and tell bundler to use the Ruby platform. You might notice that some things in the Rails installation directions are skipped… that’s because the system already has it. Proceed by installing the Rails gem:

[opc@ruby ~]$ gem install rails

Test to make sure that Rails is seen:

[opc@ruby ~]$ rails --version
Rails 7.0.1
[opc@ruby ~]$

To create a new Rails project:

[opc@ruby ~]$ rails new my_app

And, we have ignition! This is great — we now can spin-up a Rails app. There are some other things that might be needed, depending on what you’re wanting to do (like the usage of Redis or an equivalent, for turbo streams, etc.). You can take it from here. Suffice it to say, we’ve got a solid platform to build the next great Rails application on!!!

Automating this

This could all be automated as part of the cloud-init for a compute instance. This can copied-and-pasted directly into the OCI Console when spinning-up the compute instance. Here’s how to do that. When you’re creating the Compute instance, before you click the “Create” button, click on “Show Advanced Options” (at the bottom of the screen):

Then in the “Management” tab, select the “Paste cloud-init script” radio selection (under the “Initialization script” heading):

And paste the following text into the text box:

#cloud-configruncmd:
  - dnf config-manager --set-enabled ol8_codeready_builder
  - dnf update -y && dnf upgrade -y
  - dnf groupinstall -y 'Development Tools'
  - dnf install -y openssl-devel libffi-devel readline-devel gdbm-devel ncurses-devel libyaml-devel
  - su - opc -c "curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash"
  - su - opc -c "echo 'PATH=\$HOME/.rbenv/bin:\$PATH' >> ~/.bashrc"
  - su - opc -c "echo 'export PATH' >> ~/.bashrc"
  - su - opc -c "echo 'eval \"\$(rbenv init - bash)\"' >> ~/.bash_profile"
  - su - opc -c "echo 'export RUBY_CONFIGURE_OPTS=\"--disable-dtrace\"' >> ~/.bashrc"
  # update this to whatever version of Ruby you'd like installed (this will be outdated and potentially unsafe very quickly)
  - su - opc -c "rbenv install 3.1.0"
  - su - opc -c "rbenv global 3.1.0"
  # begin Rails part - remove if not needed
  - dnf install -y nodejs sqlite-devel
  - npm i -g corepack
  - su - opc -c "bundle config set force_ruby_platform true"
  - su - opc -c "gem install rails"
  # let us know things are all done
  - su - opc -c "echo '1' >> ~/cloud-init-status.txt"

That’ll pass the above to the instance. If you’d prefer, embed this in your Terraform resource definition for the compute instance:

resource "oci_core_instance" "my_compute" {
  ...
  metadata {
      ...
      user_data = "${base64encode(file("ror.tpl"))}"
      ...
  }
  ...
}

And here’s what’s in the ror.tpl file (notice it’s the same content as above — what we would’ve copied into the OCI Console when creating the instance):

#cloud-configruncmd:
  - dnf config-manager --set-enabled ol8_codeready_builder
  - dnf update -y && dnf upgrade -y
  - dnf groupinstall -y 'Development Tools'
  - dnf install -y openssl-devel libffi-devel readline-devel gdbm-devel ncurses-devel libyaml-devel
  - su - opc -c "curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash"
  - su - opc -c "echo 'PATH=\$HOME/.rbenv/bin:\$PATH' >> ~/.bashrc"
  - su - opc -c "echo 'export PATH' >> ~/.bashrc"
  - su - opc -c "echo 'eval \"\$(rbenv init - bash)\"' >> ~/.bash_profile"
  - su - opc -c "echo 'export RUBY_CONFIGURE_OPTS=\"--disable-dtrace\"' >> ~/.bashrc"
  # update this to whatever version of Ruby you'd like installed (this will be outdated and potentially unsafe very quickly)
  - su - opc -c "rbenv install 3.1.0"
  - su - opc -c "rbenv global 3.1.0"
  # begin Rails part - remove if not needed
  - dnf install -y nodejs sqlite-devel
  - npm i -g corepack
  - su - opc -c "bundle config set force_ruby_platform true"
  - su - opc -c "gem install rails"
  # let us know things are all done
  - su - opc -c "echo '1' >> ~/cloud-init-status.txt"

The end result is the same: you get a fully-functioning, ready-to-go OCI Compute instance with Ruby, rbenv and Rails ready-to-go. If you don’t want Rails, omit that part… you get the idea.

If you’ve not used it a lot before, know that cloud-init runs in the background (largely silently), which is why we write to a file when we’re all done. It’ll be happily be running in the background for a bit. If you’d like to monitor what it’s doing, try this:

[opc@ruby ~]$ sudo tail -f /var/log/cloud-init-output.log

This will show you what it’s doing. It’ll likely take a few minutes after the instance is created for it to complete. Patience is key, but rest assured that you’ll get a ready-to-go instance in just a few minutes. If you don’t open a new shell session after cloud-init is fully completed, don’t forget to run:

[opc@ruby ~]$ source ~/.bash_profile

Otherwise you can exit your shell (likely SSH) session and start a new, at which point you should have Ruby accessible and ready-to-go!

Conclusion

We’ve now got a great platform for Ruby development. Not only can we set it up manually, but there’s a fully automated way of making this happen. How cool is that?! Until next time, happy coding and keep the bits flowing!

Join the conversation!

If you’re curious about the goings-on of Oracle Developers in their natural habitat, come join us on our public Slack channel! We don’t mind being your fish bowl 🐠

Photo by Christina Morillo from Pexels

Tim Clegg


Previous Post

Go With The Flow

Paul Guerin | 5 min read

Next Post


Connecting a Go application to Oracle Database

Lucas Jellema | 11 min read