Platform.sh User Documentation

Ruby

Upsun Beta

Access our newest offering - Upsun!

Get your free trial by clicking the link below.

Get your Upsun free trial

Platform.sh supports deploying any Ruby application. Your application can use any Ruby application server such as Unicorn or Puma and deploying a Rails or a Sinatra app is very straight forward.

Supported versions Anchor to this heading

You can select the major and minor version.

Patch versions are applied periodically for bug fixes and the like. When you deploy your app, you always get the latest available patches.

Ruby MRI Anchor to this heading

Grid Dedicated Gen 3 Dedicated Gen 2
  • 3.3
  • 3.2
  • 3.1
  • 3.0
None available None available

Specify the language Anchor to this heading

To use Ruby, specify ruby as your app’s type:

.platform.app.yaml
type: 'ruby:<VERSION_NUMBER>'

For example:

.platform.app.yaml
type: 'ruby:3.3'

Unicorn based Rails configuration Anchor to this heading

This example uses Unicorn to run a Ruby application. You could use any Ruby application server such as Puma or Thin.

Configure the .platform.app.yaml file with a few key settings as listed below. A complete example is included at the end of this section.

  1. Specify the language of your application (available versions are listed above):
.platform.app.yaml
type: 'ruby:3.3'
  1. Setup environment variables.

    Rails runs by default on a preview environment. You can change the Rails/Bundler via those environment variables, some of which are defaults on Platform.sh.

.platform.app.yaml
variables:
    env:
        BUNDLE_CACHE_ALL: '1'
        BUNDLE_CLEAN: '1' # /!\ if you are working with Ruby <2.7, this doesn't work well
        BUNDLE_DEPLOYMENT: '1'
        BUNDLE_ERROR_ON_STDERR: '1'
        BUNDLE_WITHOUT: 'development:test'
        DEFAULT_BUNDLER_VERSION: "2.2.26" # in case none is mentioned in Gemfile.lock
        EXECJS_RUNTIME: 'Node'
        NODE_ENV: 'production'
        NODE_VERSION: v14.17.6
        NVM_VERSION: v0.38.0
        RACK_ENV: 'production'
        RAILS_ENV: 'production'
        RAILS_LOG_TO_STDOUT: '1' # log to /var/log/app.log
        RAILS_TMP: '/tmp'

The SECRET_KEY_BASE variable is generated automatically based on the PLATFORM_PROJECT_ENTROPY variable. You can change it.

  1. Build your application with the build hook.

    Assuming you have your dependencies stored in the Gemfile at your app root, create a hook like the following:

.platform.app.yaml
hooks:
    build: |
        set -e

        echo "Installing NVM $NVM_VERSION"
        unset NPM_CONFIG_PREFIX
        export NVM_DIR="$PLATFORM_APP_DIR/.nvm"
        # install.sh automatically installs NodeJS based on the presence of $NODE_VERSION
        curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh | bash
        [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

        # we install the bundled bundler version and fallback to a default (in env vars above)
        export BUNDLER_VERSION="$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" || $DEFAULT_BUNDLER_VERSION
        echo "Install bundler $BUNDLER_VERSION"
        gem install --no-document bundler -v $BUNDLER_VERSION

        echo "Installing gems"
        # We copy the bundle directory to the Platform.sh cache directory for
        # safe keeping, then restore from there on the next build. That allows
        # bundler to skip downloading code it doesn't need to.
        [ -d "$PLATFORM_CACHE_DIR/bundle" ] && \
            rsync -az --delete "$PLATFORM_CACHE_DIR/bundle/" vendor/bundle/
        mkdir -p "$PLATFORM_CACHE_DIR/bundle"
        bundle install
        # synchronize updated cache for next build
        [ -d "vendor/bundle" ] && \
            rsync -az --delete vendor/bundle/ "$PLATFORM_CACHE_DIR/bundle/"

        # precompile assets
        echo "Precompiling assets"
        # We copy the webpacker directory to the Platform.sh cache directory for
        # safe keeping, then restore from there on the next build. That allows
        # bundler to skip downloading code it doesn't need to.
        mkdir -p "$PLATFORM_CACHE_DIR/webpacker"
        mkdir -p "$RAILS_TMP/cache/webpacker"
        [ -d "$PLATFORM_CACHE_DIR/webpacker" ] && \
            rsync -az --delete "$PLATFORM_CACHE_DIR/webpacker/" $RAILS_TMP/cache/webpacker/
        # We dont need secret here https://github.com/rails/rails/issues/32947
        SECRET_KEY_BASE=1 bundle exec rails assets:precompile
        rsync -az --delete $RAILS_TMP/cache/webpacker/ "$PLATFORM_CACHE_DIR/webpacker/"        
    deploy: bundle exec rake db:migrate

These are installed as your project dependencies in your environment. You can also use the dependencies key to install global dependencies. These can be Ruby, Python, NodeJS, or PHP libraries.

If you have assets, it’s likely that you need NodeJS/yarn.

.platform.app.yaml
dependencies:
    nodejs:
        yarn: "*"
  1. Configure the command to start serving your application (this must be a foreground-running process) under the web section:
.platform.app.yaml
web:
    upstream:
        socket_family: unix
    commands:
        start: "bundle exec unicorn -l $SOCKET"

This assumes you have Unicorn as a dependency in your Gemfile:

# Use Unicorn as the app server
gem "unicorn", "~> 6.0", :group => :production
  1. Define the web locations your application is using:
.platform.app.yaml
web:
    locations:
        "/":
            root: "public"
            passthru: true
            expires: 1h
            allow: true

This configuration sets the web server to handle HTTP requests at /static to serve static files stored in /app/static/ folder. Everything else is forwarded to your application server.

  1. Create any Read/Write mounts. The root file system is read only. You must explicitly describe writable mounts.
.platform.app.yaml
mounts:
    "/log":
        source: local
        source_path: log
    "/storage":
        source: local
        source_path: storage
    "/tmp":
        source: local
        source_path: tmp

This setting allows your application writing temporary files to /app/tmp, logs stored in /app/log, and active storage in /app/storage.

You can define other read/write mounts (your application code itself being deployed to a read-only file system). Note that the file system is persistent and when you backup your cluster these mounts are also backed up.

  1. Then, setup the routes to your application in .platform/routes.yaml.
.platform.app.yaml
"https://{default}/":
    type: upstream
    upstream: "app:http"

Complete app configuration Anchor to this heading

Here is a complete .platform.app.yaml file:

.platform.app.yaml
name: 'app'

type: "ruby:3.0"

dependencies:
    nodejs:
        yarn: "*"

relationships:
    database: "database:mysql"

disk: 2048

variables:
    env:
        BUNDLE_CACHE_ALL: '1'
        BUNDLE_CLEAN: '1' # /!\ if you are working with Ruby<2.7 this doesn't work well
        BUNDLE_DEPLOYMENT: '1'
        BUNDLE_ERROR_ON_STDERR: '1'
        BUNDLE_WITHOUT: 'development:test'
        DEFAULT_BUNDLER_VERSION: "2.2.26" # in case none is mentioned in Gemfile.lock
        EXECJS_RUNTIME: 'Node'
        NODE_ENV: 'production'
        NODE_VERSION: v14.17.6
        NVM_VERSION: v0.38.0
        RACK_ENV: 'production'
        RAILS_ENV: 'production'
        RAILS_LOG_TO_STDOUT: '1'
        RAILS_TMP: '/tmp'

hooks:
    build: |
        set -e

        echo "Installing NVM $NVM_VERSION"
        unset NPM_CONFIG_PREFIX
        export NVM_DIR="$PLATFORM_APP_DIR/.nvm"
        # install.sh will automatically install NodeJS based on the presence of $NODE_VERSION
        curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh | bash
        [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

        # we install the bundled bundler version and fallback to a default (in env vars above)
        export BUNDLER_VERSION="$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" || $DEFAULT_BUNDLER_VERSION
        echo "Install bundler $BUNDLER_VERSION"
        gem install --no-document bundler -v $BUNDLER_VERSION

        echo "Installing gems"
        # We copy the bundle directory to the Platform.sh cache directory for
        # safe keeping, then restore from there on the next build. That allows
        # bundler to skip downloading code it doesn't need to.
        [ -d "$PLATFORM_CACHE_DIR/bundle" ] && \
            rsync -az --delete "$PLATFORM_CACHE_DIR/bundle/" vendor/bundle/
        mkdir -p "$PLATFORM_CACHE_DIR/bundle"
        bundle install
        # synchronize updated cache for next build
        [ -d "vendor/bundle" ] && \
            rsync -az --delete vendor/bundle/ "$PLATFORM_CACHE_DIR/bundle/"

        # precompile assets
        echo "Precompiling assets"
        # We copy the webpacker directory to the Platform.sh cache directory for
        # safe keeping, then restore from there on the next build. That allows
        # bundler to skip downloading code it doesn't need to.
        mkdir -p "$PLATFORM_CACHE_DIR/webpacker"
        mkdir -p "$RAILS_TMP/cache/webpacker"
        [ -d "$PLATFORM_CACHE_DIR/webpacker" ] && \
            rsync -az --delete "$PLATFORM_CACHE_DIR/webpacker/" $RAILS_TMP/cache/webpacker/
        # We dont need secret here https://github.com/rails/rails/issues/32947
        SECRET_KEY_BASE=1 bundle exec rails assets:precompile
        rsync -az --delete $RAILS_TMP/cache/webpacker/ "$PLATFORM_CACHE_DIR/webpacker/"        
    deploy: bundle exec rake db:migrate

mounts:
    "/log":
        source: local
        source_path: log
    "/storage":
        source: local
        source_path: storage
    "/tmp":
        source: local
        source_path: tmp

web:
    upstream:
        socket_family: unix
    commands:
        start: "bundle exec unicorn -l $SOCKET"

    locations:
        "/":
            root: "public"
            passthru: true
            expires: 1h
            allow: true

Configuring services Anchor to this heading

This example assumes there is a MySQL instance. To configure it, create a service such as the following:

.platform/services.yaml
database:
    type: mysql:11.2
    disk: 2048

Connecting to services Anchor to this heading

Once you have a service, link to it in your app configuration:

.platform.app.yaml
relationships:
    database: "database:mysql"

By using the following Ruby function calls, you can obtain the database details.

require "base64"
require "json"
relationships= JSON.parse(Base64.decode64(ENV['PLATFORM_RELATIONSHIPS']))

This should give you something like the following:

{
   "database" : [
      {
         "path" : "main",
         "query" : {
            "is_master" : true
         },
         "port" : 3306,
         "username" : "user",
         "password" : "",
         "host" : "database.internal",
         "ip" : "246.0.241.50",
         "scheme" : "mysql"
      }
   ]
}

For Rails, you have two choices:

  • Use the standard Rails config/database.yml with the values found with the snippet provided before
  • Use the platformsh-rails-helper gem by adding it to your Gemfile and commenting the production block in config/database.yml
### Configuration reader While you can read the environment directly from your app, you might want to use the [helper library for Ruby apps](https://github.com/platformsh/platformsh-ruby-helper) or [one for Rails apps](https://github.com/platformsh/platformsh-rails-helper) . It decodes service credentials, the correct port, and other information for you.

Other tips Anchor to this heading

  • To speed up boot you can use the Bootsnap gem and configure it with the local /tmp:

    config/boot.rb
    Bootsnap.setup(cache_dir: "/tmp/cache")
  • For garbage collection tuning, you can read this article and look for discourse configurations

  • New images are released on a regular basis to apply security patches. To avoid issues when such updates are performed, use ruby "~>3.1" in your Gemfile.

Project templates Anchor to this heading

The following list shows templates available for Ruby apps.

A template is a starting point for building your project. It should help you get a project ready for production.

Templates in development.

Troubleshooting Anchor to this heading

By default, deployments have BUNDLE_DEPLOYMENT=1 to ensure projects have a Gemfile.lock file. This is safer for version yank issues and other version upgrade breakages.

You may encounter an error like the following during a build:

W: bundler: failed to load command: rake (/app/.global/bin/rake)
W: /app/.global/gems/bundler-2.3.5/lib/bundler/resolver.rb:268:in `block in verify_gemfile_dependencies_are_found!': Could not find gem 'rails (= 5.2.6)' in locally installed gems. (Bundler::GemNotFound)

To resolve this error:

  1. Run bundle install with the same ruby and bundler versions defined in your .platform.app.yaml file.
  2. Push the Gemfile.lock to your repository.

Is this page helpful?