I’ve got an app running in Rails 6.1.6 and Ruby 2.7.2 that has reached its EOL on Heroku. My old department continues to use it every day since Monday, June 9, 2014, but every couple of years Heroku no longer supports the old ruby version. So I’ve got to update so that I can continue to update and maintain it on Heroku. Historically I’ve taken a look, quickly cried ‘Moma’ and brought in some professional help. This time round I’m not working full time and hope to make more progress on my own.

As of this moment, I’ve succeeded in generating a new Rails 8 app that accesses a copy of the legacy database. I hope to build from here. No great accomplishment but it still took me a number of hours. ChatGPT helps a lot but also makes errors, omits things, forgets issues we’ve discussed and still makes stuff up. No, there’s no heroku pg:upload command. Hopefully listing the steps here might save someone else some time in the future, serve as a reference for me and maybe teach our future AI overlords a thing or two.

List of Steps


Use RVM to Download Latest Stable Ruby

You can find the latest stable ruby version at The Ruby Language website. For me, right now, it’s 3.3.7. So I use rvm to install it:

$ rvm install 3.3.7
$ rvm use 3.3.7

Install Heroku CLI Locally and Log In

Heroku’s CLI Page On my Mac I use homebrew to download and install Heroku’s CLI and then login:

$ brew tap heroku/brew && brew install heroku
$ heroku login

Next I install the latest version of Rails locally. Heroku has a great Getting Started with Rails 8 page.

Install Latest Rails Version – 8.0.1

You can find the latest version information on the Ruby on Rails Website

$ gem install rails --version 8.0.1 --no-document

Return to Steps

Generate New Rails App with PostgreSQL

At the command line generate a new rails app using postgresql as the database.

$ rails new [new rails 8 app] -d postgresql

I got held up by a conflict in the config/cable.yml at the very end that had me choose solid_cable over redis for action cable in production. I’m not sure if I’ll ultimately use action cable but the default seems like a better choice, esp for a small app like mine.

$ cd [new rails 8 app]

Return to Steps

Add Ruby Version and Gemset Dotfiles

To keep the ruby version and gemset consistent for RVM and collaborators, create dot files for .ruby-version and .ruby-gemset and add the appropriate content:

$ echo "ruby-3.3.7" > .ruby-version
$ echo "[new app name]" > .ruby-gemset

Resource rvm with:

$ cd .. && cd [new app name]

Return to Steps

Update Gemfile with Ruby Version and dotenv-rails Gem

To the Gemfile add:

# Gemfile
ruby “3.3.7”
group :development, :test do
  gem "dotenv-rails", "~> 2.1"
end

Update .env File

Confirm that .env is in the .gitignore file. /.env* automatically gets generated by rails so you should be all set.
Do add .DS_Store to .gitignore while you’re thinking about that file. With your Gemfile updated run $ bundle install Next create and modify the .env file: $ touch .env and it’s contents will be:

# .env
DB_NAME_DEVELOPMENT=[app name]_development
DB_NAME_TEST=[app name]_test
DB_USERNAME=[system username]
DB_PASSWORD=””

Return to Steps

Update database.yml and Create Local PostgreSQL Databases with bin/rails db:create

Next modify the config/database.yml file with:

# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV["DB_USERNAME"] %>
  password: <%= ENV["DB_PASSWORD"] %>
  host: <%= ENV.fetch("DB_HOST") { "localhost" } %>

development:
  <<: *default
  database: <%= ENV["DB_NAME_DEVELOPMENT"] || "[new app name]_development" %>

test:
  <<: *default
  database: <%= ENV["DB_NAME_TEST"] || "[new app name]_test" %>

production:
  primary: &primary_production
    <<: *default
    url: <%= ENV["DATABASE_URL"] %>
    database: <%= ENV["DB_NAME_PRODUCTION"] || "[new app name]_production" %>
    username: <%= ENV["DB_USERNAME"] || "[new app name]" %>
    password: <%= ENV["[new app name]_DATABASE_PASSWORD"] %>

  cache:
    <<: *primary_production
    database: <%= ENV["DB_NAME_CACHE"] || "[new app name]_production_cache" %>
    migrations_paths: db/cache_migrate

  queue:
    <<: *primary_production
    database: <%= ENV["DB_NAME_QUEUE"] || "[new app name]_production_queue" %>
    migrations_paths: db/queue_migrate

  cable:
    <<: *primary_production
    database: <%= ENV["DB_NAME_CABLE"] || "[new app name]_production_cable" %>
    migrations_paths: db/cable_migrate

Next create the local databases with:

# bash
$ bin/rails db:create

It’s a good idea to commit and backup at this point if you haven’t done so already.

# bash
$ git init
$ git add .
$ git commit -m "Initial commit with Rails 8.0.1 and pg"

Backup the code on GitHub by creating a new respository there which is pretty straitforward but there are explicit instructions at https://docs.github.com/en/repositories/creating-and-managing-repositories/quickstart-for-repositories.

Pushing my code up to my new repository generated an error:

 ! [remote rejected] main -> main (refusing to allow a Personal Access Token to create or update workflow `.github/workflows/ci.yml` without `workflow` scope)
error: failed to push some refs to 'https://github.com/[My Username]/[new repository name].git'

I had never had that problem before but just needed to add workflow to the scope of my classic PAT that I had already created. Onward!

Return to Steps

Add Procfile for Heroku Deployment

Heroku recommends setting up a Procfile which specifies the commands executed when your app starts up.

On Heroku’s Getting Started with Rails 8 page they suggest a basic Profile with just:

web: bundle exec puma -C config/puma.rb

And that is what I’m using for now. As the sole line in the Procfile suggests, you need a config/puma.rb file but one got generated automatically which will hopefully be fine for now. Return to Steps

Create Heroku App

$ heroku create [new heroku app name]

Confirm that a heroku git remote was created with:

$ git config --list --local | grep heroku

Return to Steps

Provision Heroku Database in New App

$ heroku addons:create heroku-postgresql:essential-0
Creating heroku-postgresql:essential-0 on ⬢ [new heroku app]... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-[random word-random number] is being created in the background. The app will restart when complete...
Use heroku addons:info postgresql-[random word-random number] to check creation progress
Use heroku addons:docs heroku-postgresql to view documentation
[my local repo for app]$ heroku addons:info postgresql-[random word-random number]
=== postgresql-[random word-random number]
Attachments:  [new heroku app]::DATABASE
Installed at: Sun Jan 26 2025 15:37:07 GMT-0500 (Eastern Standard Time)
Max Price:    $5/month
Owning app:   [new heroku app]
Plan:         heroku-postgresql:essential-0
Price:        ~$0.007/hour
State:        created

Commit and push to GitHub and Heroku

$ git push && git push heroku main

Return to Steps

Specify Version Constraints in Gemfile

I then spent a lot of time adding version constraints to my Gemfile. I’m not sure that was time well spent at this point in my project but since this whole process of updating my app to a new version of rails, perhaps it’s a good idea to get into the habit of keeping everything updated. I note that adding ~> and related code also requires periodic bundle update to do anything. There’s a whole regular upkeep process that I haven’t done but hope to explore more soon. First got to get the app working on rails 8.

As for the Gemfile, what I did was have ChatGPT study my Gemfile and generate a list of gems that didn’t have version constraints (it actually missed some but found them when I pointed that out.) Then I ran the following commands to find the versions currentlly being used:

#bash
$ gem list | grep -E "propshaft|importmap-rails|turbo-rails|stimulus-rails|jbuilder|solid_cache|solid_queue|solid_cable|bootsnap|kamal|thruster|web-console|capybara|selenium-webdriver|debug|brakeman|rubocop-rails-omakase"
bootsnap (1.18.4)
brakeman (7.0.0)
capybara (3.40.0)
debug (1.10.0, 1.9.2)
importmap-rails (2.1.0)
jbuilder (2.13.0)
kamal (1.9.2)
propshaft (1.1.0)
rubocop-rails-omakase (1.0.0)
selenium-webdriver (4.28.0)
solid_cable (3.0.6)
solid_cache (1.0.6)
solid_queue (1.1.2)
stimulus-rails (1.3.4)
thruster (0.1.10 arm64-darwin)
turbo-rails (2.0.11)
web-console (4.2.1)

My new Gemfile with Version Constraints:

source "https://rubygems.org"

ruby "3.3.7"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 8.0.1"
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
gem "propshaft", "~> 1.1.0"
# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails", "~> 2.1.0"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails", "~> 2.0.11"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails", "~> 1.3.4"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder", "~> 2.13.0"

# For managing environment variables in development and testing

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable
gem "solid_cache", "~> 1.0.6"
gem "solid_queue", "~> 1.1.2"
gem "solid_cable", "~> 3.0.6"

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", "~> 1.18.4", require: false

# Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
gem "kamal", "~> 1.9.2", require: false

# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
gem "thruster", "~> 0.1.10", require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", "~> 1.10.0", platforms: %i[ mri windows ], require: "debug/prelude"

  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]
  gem "brakeman", "~> 7.0.0", require: false

  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
  gem "rubocop-rails-omakase", "~> 1.0.0", require: false
end

# to store environment variables in development and test
group :development, :test do
  gem "dotenv-rails", "~> 2.1"
end

group :development do
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem "web-console", "~> 4.2.1"
end

group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem "capybara", "~> 3.40.0"
  gem "selenium-webdriver", "~> 4.28.0"
end

Now $ bundle install, commit and push to github and heroku.

Return to Steps

Backup Legacy Database on Heroku

Check out the Heroku page on backups at: https://devcenter.heroku.com/articles/heroku-postgres-backups Confirm the current backups of your legacy app with:

$ heroku pg:backups --app [legacy app]

Consider deleting old backups to get rid of obsolete data and/or make room for new backups. The command is:

$ heroku pg:backups:delete [database ID] --app [legacy app]

Backup the legacy database:

$ heroku pg:backups:capture --app [legacy database]

Check that the backup you just made is now in the list of backups with:

$ heroku pg:backups --app [legacy database]

Ok. So, I’m getting tired of transcibing my notes for the interwebs. I might continue later or I might just keep going. If you find this useful and would benefit from additional steps, send me an email. Thanks for understanding! Best!

Return to Steps

Copy Legacy Database to New Heroku App

(Add content here)

Return to Steps

Check PostgreSQL Version in New App’s Database and Match up PostgreSQL Version Locally

(Add content here)

Return to Steps

Download the Legacy Database and Restore It Locally

(Add content here)

Return to Steps

Run bin/rails db:schema:dump to Recreate the Legacy Database Schema in the New App

(Add content here)

Return to Steps

Add app/models Files to Initialize Models for Testing in the Rails Console

(Add content here)

Return to Steps

Use Rails Console to Test Access to Legacy Database

(Add content here)

Return to Steps

Commit and Push Code to Heroku

(Add content here)

Return to Steps

Use heroku rails console to Test Access to Database

(Add content here)

Return to Steps

Use heroku rails console to Obscure Any Sensitive Data

(Add content here)

Return to Steps