Table of Contents [expand]
- Introduction
- Set Up
- Clone the Sample App
- Define a Procfile
- Create Your App
- Provision a Database
- Deploy the App
- View Logs
- Scale the App
- Declare App Dependencies
- Run the App Locally
- Push Local Changes
- Provision a Logging Add-on
- Start a Console
- Define Config Vars
- Use a Database
- Delete Your App
- Next Steps
Last updated April 28, 2026
Introduction
Complete this tutorial to deploy a sample Ruby app to Cedar, the legacy generation of the Heroku platform. To deploy the app to the Fir generation, only available to Heroku Private Spaces, follow this guide instead.
The tutorial assumes that you have:
- A verified Heroku Account
- An Eco dynos plan subscription (recommended)
- Ruby 3.4.9 installed locally - see the installation guides for Ruby and Rails on macOS, Windows, and Linux
- Bundler installed locally - run
gem install bundler - Postgres installed locally
Using dynos and databases to complete this tutorial counts towards your usage. We recommend using our low-cost plans to complete this tutorial. Eligible students can apply for platform credits through our new Heroku for GitHub Students program.
Set Up
Install the Heroku Command Line Interface (CLI). Use the CLI to manage and scale your app, provision add-ons, view your logs, and run your app locally.
The Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, complete the following before proceeding:
Download and run the installer for your platform:
Download the appropriate installer for your Windows installation:
You can find more installation options for the Heroku CLI here.
After installation, you can use the heroku command from your command shell.
To log in to the Heroku CLI, use the heroku login command:
$ heroku login
heroku: Press any key to open up the browser to login or q to exit:
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com
This command opens your web browser to the Heroku login page. If your browser is already logged in to Heroku, click the Log In button on the page.
This authentication is required for the heroku and git commands to work correctly.
If you have any problems installing or using the Heroku CLI, see the main Heroku CLI article for advice and troubleshooting steps.
If you’re behind a firewall that uses a proxy to connect with external HTTP/HTTPS services, set the HTTP_PROXY or HTTPS_PROXY environment variables in your local development environment before running the heroku command.
Clone the Sample App
If you’re new to Heroku, it’s recommended that you complete this tutorial using the Heroku-provided sample application.
To deploy an existing application, follow this article instead.
Clone the sample application to get a local version of the code. Execute these commands in your local command shell or terminal:
$ git clone https://github.com/heroku/ruby-getting-started.git
$ cd ruby-getting-started
You now have a functioning Git repository that contains a simple application. It includes a Gemfile file, which Ruby’s dependency manager, bundler, uses to install dependencies.
Define a Procfile
Use a Procfile, a text file in the root directory of your application, to explicitly declare what command to execute to start your app.
The Procfile in the example app looks like this:
web: bundle exec puma -C config/puma.rb
This Procfile declares a single process type, web, and the command needed to run it. The name web is important here. It declares that this process type is attached to Heroku’s HTTP routing stack and receives web traffic when deployed. The command used here runs Puma, the web server, and passes in a configuration file.
A Procfile can contain additional process types. For example, you can declare a background worker process that processes items off a queue.
Create Your App
Using a dyno and a database to complete this tutorial counts towards your usage. Delete your app, and database as soon as you’re done to control costs.
Apps use Eco dynos if you’re subscribed to Eco by default. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account. It’s recommended if you plan on deploying many small apps to Heroku. Learn more here. Eligible students can apply for platform credits through our Heroku for GitHub Students program.
Create an app on Heroku to prepare the platform to receive your source code:
$ heroku create
Creating app... done, shielded-stream-86785
https://shielded-stream-86785-c93748b24632.herokuapp.com/ | https://git.heroku.com/shielded-stream-86785.git
When you create an app, a Git remote named heroku is also created and added to your local repository configuration. Git remotes are versions of your repository that live on other servers. You can deploy your app by pushing code to that special Heroku-hosted remote associated with your app.
Heroku generates a random name for your app, in this case, shielded-stream-86785. You can specify your own app name.
Provision a Database
The sample app requires a database. Provision a Heroku Postgres database, an add-on available through the Elements Marketplace. Add-ons are cloud services that provide out-of-the-box additional services for your application, such as logging, monitoring, databases, and more.
An essential-0 Postgres size costs $5 a month, prorated to the minute. At the end of this tutorial, we prompt you to delete your database to minimize costs.
$ heroku addons:create heroku-postgresql:essential-0
Creating heroku-postgresql:essential-0 on shielded-stream-86785... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-solid-73058 is being created in the background. The app will restart when complete...
Run heroku addons:info postgresql-solid-73058 to check creation progress.
Run heroku addons:docs heroku-postgresql to view documentation.
You can wait for the database to provision by running this command:
$ heroku pg:wait
After that command exits, your Heroku app can access the Postgres database. The DATABASE_URL environment variable stores your credentials, which your app is configured to connect to. You can see all the add-ons provisioned with the addons command:
$ heroku addons
Add-on Plan Price Max Price State
───────────────────────────────────────────────────────────────────────────────────────────────
heroku-postgresql (postgresql-solid-73058) essential-0 ~$0.007/hour $5/month created
└─ as DATABASE
The table above shows add-ons and the attachments to the current app (shielded-stream-86785) or other apps.
Deploy the App
Using a dyno to complete this tutorial counts towards your usage. Delete your app and database as soon as you’re done to control costs.
Deploy your code. This command pushes the main branch of the sample repo to your heroku remote, which then deploys to Heroku:
$ git push heroku main
remote: Updated 98 paths from d8ec562
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-24 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Ruby app detected
remote: -----> Using Ruby version: ruby-3.4.8
remote: -----> Installing bundler 4.0.5
remote: -----> Installing dependencies using bundler 4.0.5
remote: Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
remote: Fetching gem metadata from https://rubygems.org/.........
remote: Fetching rake 13.3.1
remote: Installing rake 13.3.1
remote: Fetching base64 0.3.0
remote: Fetching bigdecimal 4.1.0
remote: Fetching concurrent-ruby 1.3.6
remote: Fetching connection_pool 3.0.2
remote: Installing base64 0.3.0
remote: Fetching drb 2.2.3
remote: Installing bigdecimal 4.1.0 with native extensions
remote: Installing connection_pool 3.0.2
remote: Installing drb 2.2.3
remote: Fetching json 2.19.3
remote: Fetching logger 1.7.0
remote: Installing concurrent-ruby 1.3.6
remote: Installing logger 1.7.0
remote: Installing json 2.19.3 with native extensions
remote: Fetching prism 1.9.0
remote: Fetching securerandom 0.4.1
remote: Installing securerandom 0.4.1
remote: Installing prism 1.9.0 with native extensions
remote: Fetching uri 1.1.1
remote: Installing uri 1.1.1
remote: Fetching builder 3.3.0
remote: Installing builder 3.3.0
remote: Fetching erubi 1.13.1
remote: Installing erubi 1.13.1
remote: Fetching racc 1.8.1
remote: Installing racc 1.8.1 with native extensions
remote: Fetching crass 1.0.6
remote: Installing crass 1.0.6
remote: Fetching rack 3.2.6
remote: Installing rack 3.2.6
remote: Fetching useragent 0.16.11
remote: Installing useragent 0.16.11
remote: Fetching prettyprint 0.2.0
remote: Installing prettyprint 0.2.0
remote: Fetching erb 6.0.2
remote: Installing erb 6.0.2 with native extensions
remote: Fetching date 3.5.1
remote: Installing date 3.5.1 with native extensions
remote: Fetching stringio 3.2.0
remote: Installing stringio 3.2.0 with native extensions
remote: Fetching tsort 0.2.0
remote: Installing tsort 0.2.0
remote: Fetching io-console 0.8.2
remote: Installing io-console 0.8.2 with native extensions
remote: Fetching thor 1.5.0
remote: Installing thor 1.5.0
remote: Fetching zeitwerk 2.7.5
remote: Installing zeitwerk 2.7.5
remote: Fetching nio4r 2.7.5
remote: Installing nio4r 2.7.5 with native extensions
remote: Fetching websocket-extensions 0.1.5
remote: Installing websocket-extensions 0.1.5
remote: Fetching timeout 0.6.0
remote: Installing timeout 0.6.0
remote: Fetching marcel 1.1.0
remote: Installing marcel 1.1.0
remote: Fetching mini_mime 1.1.5
remote: Installing mini_mime 1.1.5
remote: Fetching msgpack 1.8.0
remote: Installing msgpack 1.8.0 with native extensions
remote: Fetching ffi 1.17.3 (x86_64-linux-gnu)
remote: Installing ffi 1.17.3 (x86_64-linux-gnu)
remote: Fetching rb-fsevent 0.11.2
remote: Installing rb-fsevent 0.11.2
remote: Fetching pg 1.6.3 (x86_64-linux)
remote: Installing pg 1.6.3 (x86_64-linux)
remote: Fetching rack-timeout 0.7.0
remote: Installing rack-timeout 0.7.0
remote: Fetching i18n 1.14.8
remote: Installing i18n 1.14.8
remote: Fetching tzinfo 2.0.6
remote: Installing tzinfo 2.0.6
remote: Fetching nokogiri 1.19.2 (x86_64-linux-gnu)
remote: Installing nokogiri 1.19.2 (x86_64-linux-gnu)
remote: Fetching rack-session 2.1.2
remote: Installing rack-session 2.1.2
remote: Fetching rack-test 2.2.0
remote: Installing rack-test 2.2.0
remote: Fetching rackup 2.3.1
remote: Installing rackup 2.3.1
remote: Fetching pp 0.6.3
remote: Installing pp 0.6.3
remote: Fetching psych 5.3.1
remote: Installing psych 5.3.1 with native extensions
remote: Fetching websocket-driver 0.8.0
remote: Installing websocket-driver 0.8.0 with native extensions
remote: Fetching net-protocol 0.2.2
remote: Installing net-protocol 0.2.2
remote: Fetching reline 0.6.3
remote: Installing reline 0.6.3
remote: Fetching rb-inotify 0.11.1
remote: Installing rb-inotify 0.11.1
remote: Fetching loofah 2.25.1
remote: Installing loofah 2.25.1
remote: Fetching rdoc 7.2.0
remote: Fetching puma 7.2.0
remote: Installing puma 7.2.0 with native extensions
remote: Installing rdoc 7.2.0
remote: Fetching net-imap 0.6.2
remote: Installing net-imap 0.6.2
remote: Fetching net-pop 0.1.2
remote: Installing net-pop 0.1.2
remote: Fetching net-smtp 0.5.1
remote: Installing net-smtp 0.5.1
remote: Fetching listen 3.10.0
remote: Installing listen 3.10.0
remote: Fetching rails-html-sanitizer 1.7.0
remote: Installing rails-html-sanitizer 1.7.0
remote: Fetching sdoc 2.6.5
remote: Installing sdoc 2.6.5
remote: Fetching mail 2.9.0
remote: Installing mail 2.9.0
remote: Fetching bootsnap 1.23.0
remote: Installing bootsnap 1.23.0 with native extensions
remote: Fetching minitest 6.0.2
remote: Fetching irb 1.17.0
remote: Installing minitest 6.0.2
remote: Installing irb 1.17.0
remote: Fetching activesupport 8.1.2.1
remote: Installing activesupport 8.1.2.1
remote: Fetching rails-dom-testing 2.3.0
remote: Fetching globalid 1.3.0
remote: Fetching activemodel 8.1.2.1
remote: Installing rails-dom-testing 2.3.0
remote: Installing globalid 1.3.0
remote: Installing activemodel 8.1.2.1
remote: Fetching actionview 8.1.2.1
remote: Fetching activejob 8.1.2.1
remote: Installing activejob 8.1.2.1
remote: Installing actionview 8.1.2.1
remote: Fetching activerecord 8.1.2.1
remote: Installing activerecord 8.1.2.1
remote: Fetching actionpack 8.1.2.1
remote: Installing actionpack 8.1.2.1
remote: Fetching railties 8.1.2.1
remote: Fetching actioncable 8.1.2.1
remote: Fetching actionmailer 8.1.2.1
remote: Installing actioncable 8.1.2.1
remote: Installing actionmailer 8.1.2.1
remote: Installing railties 8.1.2.1
remote: Fetching propshaft 1.3.1
remote: Installing propshaft 1.3.1
remote: Fetching activestorage 8.1.2.1
remote: Installing activestorage 8.1.2.1
remote: Fetching actionmailbox 8.1.2.1
remote: Installing actionmailbox 8.1.2.1
remote: Fetching action_text-trix 2.1.18
remote: Fetching importmap-rails 2.2.3
remote: Installing action_text-trix 2.1.18
remote: Installing importmap-rails 2.2.3
remote: Fetching actiontext 8.1.2.1
remote: Installing actiontext 8.1.2.1
remote: Fetching rails 8.1.2.1
remote: Installing rails 8.1.2.1
remote: Bundle complete! 10 Gemfile dependencies, 78 gems now installed.
remote: Gems in the groups 'development' and 'test' were not installed.
remote: Bundled gems are installed into `./vendor/bundle`
remote: Bundle completed (28.57s)
remote: Cleaning up the bundler cache.
remote: Running: bundle list
remote: -----> Compiling Ruby/Rails
remote: -----> Detecting rake tasks
remote: -----> Preparing app for Rails asset pipeline
remote: Running: rake assets:precompile
remote: Generating image variants require the image_processing gem. Please add `gem "image_processing", "~> 1.2"` to your Gemfile or set `config.active_storage.variant_processor = :disabled`.
remote: Writing lang-logo-27c62977.png
remote: Writing application-f5e7b2f6.css
remote: Writing actiontext.esm-c376325e.js
remote: Writing actiontext-c9c6c481.js
remote: Writing trix-be60fa75.js
remote: Writing trix-65afdb1d.css
remote: Writing action_cable-5212cfee.js
remote: Writing actioncable.esm-e0ec9819.js
remote: Writing actioncable-ac25813f.js
remote: Writing activestorage.esm-81bb34bc.js
remote: Writing activestorage-f9e46063.js
remote: Writing rails-ujs.esm-e925103b.js
remote: Writing rails-ujs-20eaf715.js
remote: Asset precompilation completed (0.74s)
remote: Cleaning assets
remote: Running: rake assets:clean
remote: Generating image variants require the image_processing gem. Please add `gem "image_processing", "~> 1.2"` to your Gemfile or set `config.active_storage.variant_processor = :disabled`.
remote: -----> Detecting rails configuration
remote:
remote: ###### WARNING:
remote:
remote: There is a more recent Ruby version available for you to use:
remote:
remote: 3.4.9
remote:
remote: The latest version will include security and bug fixes. We always recommend
remote: running the latest version of your minor release.
remote:
remote: Please upgrade your Ruby version.
remote:
remote: For all available Ruby versions see:
remote: https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
remote:
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote: Default types for buildpack -> console, rake
remote:
remote: -----> Compressing...
remote: Done: 55.5M
remote: -----> Launching...
remote: Released v4
remote: https://shielded-stream-86785-c93748b24632.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/shielded-stream-86785.git
* [new branch] main -> main
Visit the app at the URL shown in the logs. As a shortcut, you can also open the website as follows:
$ heroku open
View Logs
Heroku treats logs as streams of time-ordered events, aggregated from the output streams of all your app and Heroku components. Heroku provides a single stream for all events.
View information about your running app by using one of the logging commands, heroku logs --tail:
$ heroku logs --tail
2026-04-28T21:22:54.303894+00:00 app[api]: Initial release by user developer@example.com2026-04-28T21:22:54.303894+00:00 app[api]: Release v1 created by user developer@example.com2026-04-28T21:22:54.543230+00:00 app[api]: Enable Logplex by user developer@example.com2026-04-28T21:22:54.543230+00:00 app[api]: Release v2 created by user developer@example.com2026-04-28T21:24:48.242812+00:00 app[api]: @ref:postgresql-solid-73058 completed provisioning, setting DATABASE_URL. by user heroku-postgresql@addons.heroku.com
2026-04-28T21:24:48.242812+00:00 app[api]: Release v3 created by user heroku-postgresql@addons.heroku.com
2026-04-28T21:24:56.000000+00:00 app[api]: Build started by user developer@example.com2026-04-28T21:25:48.812980+00:00 app[api]: Release v4 created by user developer@example.com2026-04-28T21:25:48.812980+00:00 app[api]: Deploy 5fc24b5c by user developer@example.com2026-04-28T21:25:48.830028+00:00 app[api]: Scaled to console@0:Basic rake@0:Basic web@1:Basic by user developer@example.com2026-04-28T21:25:51.734585+00:00 heroku[web.1]: Starting process with command `bundle exec puma -C config/puma.rb`
2026-04-28T21:25:52.730721+00:00 app[web.1]: [2] Puma starting in cluster mode...
2026-04-28T21:25:52.730743+00:00 app[web.1]: [2] * Puma version: 7.2.0 ("On The Corner")
2026-04-28T21:25:52.730743+00:00 app[web.1]: [2] * Ruby version: ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [x86_64-linux]
2026-04-28T21:25:52.730744+00:00 app[web.1]: [2] * Min threads: 3
2026-04-28T21:25:52.730744+00:00 app[web.1]: [2] * Max threads: 3
2026-04-28T21:25:52.730744+00:00 app[web.1]: [2] * Environment: production
2026-04-28T21:25:52.730745+00:00 app[web.1]: [2] * Master PID: 2
2026-04-28T21:25:52.730759+00:00 app[web.1]: [2] * Workers: 2
2026-04-28T21:25:52.730769+00:00 app[web.1]: [2] * Restarts: (✔) hot (✖) phased (✖) refork
2026-04-28T21:25:52.730777+00:00 app[web.1]: [2] * Preloading application
2026-04-28T21:25:54.000000+00:00 app[api]: Build succeeded
2026-04-28T21:25:55.335602+00:00 app[web.1]: Generating image variants require the image_processing gem. Please add `gem "image_processing", "~> 1.2"` to your Gemfile or set `config.active_storage.variant_processor = :disabled`.
2026-04-28T21:25:55.335613+00:00 app[web.1]: [2] * Listening on http://[::]:28986
2026-04-28T21:25:55.335698+00:00 app[web.1]: [2] Use Ctrl-C to stop
2026-04-28T21:25:55.343843+00:00 app[web.1]: [2] - Worker 0 (PID: 7) booted in 0.0s, phase: 0
2026-04-28T21:25:55.346170+00:00 app[web.1]: [2] - Worker 1 (PID: 11) booted in 0.0s, phase: 0
2026-04-28T21:25:55.885725+00:00 heroku[web.1]: State changed from starting to up
2026-04-28T21:25:59.825233+00:00 app[web.1]: source=rack-timeout id=b698cea6-cabf-7528-d8e1-df84885e799c wait=8ms timeout=15000ms state=ready
2026-04-28T21:25:59.829319+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Started GET "/" for 66.203.115.15 at 2026-04-28 21:25:59 +0000
2026-04-28T21:25:59.833872+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Processing by WelcomeController#index as */*
2026-04-28T21:25:59.850353+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Importmap skipped missing path: application.js
2026-04-28T21:25:59.850428+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Importmap skipped missing path: application.js
2026-04-28T21:25:59.851015+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Importmap skipped missing path: application.js
2026-04-28T21:25:59.851238+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Rendered layout layouts/application.html.erb (Duration: 9.5ms | GC: 0.0ms)
2026-04-28T21:25:59.851794+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] Completed 200 OK in 18ms (Views: 12.1ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
2026-04-28T21:25:59.853938+00:00 app[web.1]: [b698cea6-cabf-7528-d8e1-df84885e799c] source=rack-timeout id=b698cea6-cabf-7528-d8e1-df84885e799c wait=8ms timeout=15000ms service=29ms state=completed
2026-04-28T21:25:59.854777+00:00 heroku[router]: at=info method=GET path="/" host=shielded-stream-86785-c93748b24632.herokuapp.com request_id=b698cea6-cabf-7528-d8e1-df84885e799c fwd="123.456.789.0" dyno=web.1 connect=0ms service=36ms status=200 bytes=9031 protocol=http1.1 tls=false
2026-04-28T21:26:02.297896+00:00 app[api]: Log session created by user developer@example.com2026-04-28T21:26:07.639948+00:00 heroku[router]: at=info method=GET path="/" host=shielded-stream-86785-c93748b24632.herokuapp.com request_id=c1e3bf30-80cc-3969-ade7-4b34f42dc75f fwd="123.456.789.0" dyno=web.1 connect=0ms service=42ms status=200 bytes=9031 protocol=http1.1 tls=false
To generate more log messages, refresh the app in your browser.
To stop streaming the logs, press Control+C.
Scale the App
After deploying the sample app, it automatically runs on a single web dyno. Think of a dyno as a lightweight container that runs the command specified in the Procfile.
You can check how many dynos are running by using the ps command:
$ heroku ps
=== web (Basic): bundle exec puma -C config/puma.rb (1)
web.1: up 2026/04/28 16:25:55 -0500 (~ 14s ago)
Scaling an app on Heroku is equivalent to changing the number of running dynos. Scale the number of web dynos to zero:
$ heroku ps:scale web=0
$ heroku ps:wait
Access the app again by hitting refresh in your browser, or heroku open to open it in a web tab. You get an error message because you no longer have web dynos available to serve requests.
Scale it up again:
$ heroku ps:scale web=1
$ heroku ps:wait
By default, apps use Eco dynos if you’re subscribed to Eco. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Eco dynos sleep if they don’t receive any traffic for half an hour. This sleep behavior causes a few seconds delay for the first request upon waking. Eco dynos consume from a monthly, account-level quota of eco dyno hours. As long as you haven’t exhausted the quota, your apps can continue to run.
To avoid dyno sleeping, upgrade to a Basic or higher dyno type as described in the Dyno Types article. Upgrading to at least Standard dynos allows you to scale up to multiple dynos per process type.
Declare App Dependencies
Heroku recognizes an app as a Ruby app by the existence of a Gemfile file in the root directory.
The demo app you deployed already has a Gemfile:
source 'https://rubygems.org'
ruby '>= 3.2', '< 4.0'
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 8.1.2"
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
gem "propshaft"
# Use postgresql as the database for Active Record
gem 'pg', '~> 1.6.3'
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
...
The Gemfile file specifies the dependencies to install with your application. It also determines the version of Ruby used to run your application on Heroku.
When an app deploys, Heroku reads this file and installs the appropriate Ruby version and dependencies using the bundle install command.
To run the app locally, you must also install dependencies locally. This Gemfile dependency pg only resolves if you have Postgres installed locally. Install Postgres before you proceed.
If the command which psql returns some value on your command line, you have Postgres installed locally:
$ which psql
/usr/local/bin/psql
Run bundle install in your local directory to install the dependencies, preparing your system for running the app locally:
$ bundle install
Bundle complete! 10 Gemfile dependencies, 80 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
After installing dependencies, you can run your app locally.
Run the App Locally
The sample app uses a database, so you must create the database and table locally using the rake task:
$ bundle exec rake db:create db:migrate db:migrate:status
Created database 'ruby-getting-started_development'
Created database 'ruby-getting-started_test'
database: ruby-getting-started_development
Status Migration ID Migration Name
--------------------------------------------------
up 20140707111715 Create widgets
Now start your application locally using the heroku local command:
$ heroku local web --port=5006
[OKAY] Loaded ENV .env File as KEY=VALUE Format
(node:31022) [DEP0060] DeprecationWarning: The `util._extend` API is deprecated. Please use Object.assign() instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
4:26:18 PM web.1 | [31023] Puma starting in cluster mode...
4:26:18 PM web.1 | [31023] * Puma version: 7.2.0 ("On The Corner")
4:26:18 PM web.1 | [31023] * Ruby version: ruby 3.4.9 (2026-03-11 revision 76cca827ab) +PRISM [arm64-darwin25]
4:26:18 PM web.1 | [31023] * Min threads: 3
4:26:18 PM web.1 | [31023] * Max threads: 3
4:26:18 PM web.1 | [31023] * Environment: development
4:26:18 PM web.1 | [31023] * Master PID: 31023
4:26:18 PM web.1 | [31023] * Workers: 2
4:26:18 PM web.1 | [31023] * Restarts: (✔) hot (✖) phased (✖) refork
4:26:18 PM web.1 | [31023] * Preloading application
4:26:18 PM web.1 | [31023] * Listening on http://[::1]:5006
4:26:18 PM web.1 | [31023] Use Ctrl-C to stop
4:26:18 PM web.1 | [31023] - Worker 0 (PID: 31034) booted in 0.01s, phase: 0
4:26:18 PM web.1 | [31023] - Worker 1 (PID: 31035) booted in 0.0s, phase: 0
Just like Heroku, heroku local uses the Procfile to know what command to execute.
To see your app running locally,open http://localhost:5006 with your web browser.
To stop the app from running locally, in the CLI, press Control + C to exit.
Push Local Changes
In this step, you propagate a local change to the application to Heroku.
Modify Gemfile to include an additional dependency for the cowsay gem.
In file Gemfile, on line 4 add:
gem "cowsay"
The file now looks like this:
source 'https://rubygems.org'
ruby '>= 3.2', '< 4.0'
gem "cowsay"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
...
Modify app/views/welcome/index.erb to use the cowsay gem.
At the end of app/views/welcome/index.erb add:
<pre><%= Cowsay.say("Hello", "tux") %></pre>
Now test locally:
$ bundle install
$ heroku local --port=5006
Visit your application at http://localhost:5006. If your changes worked, you see a cute ASCII picture displayed.
Now deploy this local change to Heroku.
Almost every deploy to Heroku follows this same pattern. First, add the modified files to the local Git repository:
$ git add .
Now commit the changes to the repository:
$ git commit -m "Added cowsay gem"
[main 5903e2d] Added cowsay gem
4 files changed, 6 insertions(+)
create mode 100644 install.txt
Now deploy as before:
$ git push heroku main
Finally, check that everything is working:
$ heroku open
Provision a Logging Add-on
Beyond databases, add-ons provide many additional services for your application. In this step, you provision a free add-on to store your app’s logs.
By default, Heroku stores 1500 lines of logs from your application, but the full log stream is available as a service. Several add-on providers have logging services that provide things such as log persistence, search, and email and SMS alerts.
In this step, you provision one of these logging add-ons, Papertrail.
Provision the Papertrail logging add-on:
$ heroku addons:create papertrail
Creating papertrail on shielded-stream-86785... free
Provisioning has been successful
Created papertrail-triangular-20478
Run heroku addons:docs papertrail to view documentation.
The add-on is now deployed and configured for your app. You can list add-ons for your app with this command:
$ heroku addons
Add-on Plan Price Max Price State
───────────────────────────────────────────────────────────────────────────────────────────────
heroku-postgresql (postgresql-solid-73058) essential-0 ~$0.007/hour $5/month created
└─ as DATABASE
papertrail (papertrail-triangular-20478) choklad free free created
└─ as PAPERTRAIL
The table above shows add-ons and the attachments to the current app (shielded-stream-86785) or other apps.
To see this particular add-on in action, visit your application’s Heroku URL a few times. Each visit generates more log messages, which get routed to the Papertrail add-on. Visit the Papertrail console to see the log messages:
$ heroku addons:open papertrail
Your browser opens up a Papertrail web console, showing the latest log events. The interface lets you search and set up alerts:

Start a Console
You can run a command, typically scripts and applications that are part of your app, in a one-off dyno using the heroku run command. You can also run an interactive bash session in your app’s environment:
$ heroku run bash
Running bash on shielded-stream-86785... starting, run.1958
Running bash on shielded-stream-86785... connecting, run.1958
Running bash on shielded-stream-86785... up, run.1958
~ $ ruby -v
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [x86_64-linux]
~ $ rails -v
Rails 8.1.2.1
~ $ ls -lah
total 100K
drwx------ 15 u29950 dyno 4.0K Apr 28 21:26 .
drwxr-xr-x 11 root root 4.0K Apr 22 14:59 ..
drwx------ 8 u29950 dyno 4.0K Apr 28 21:24 app
-rw------- 1 u29950 dyno 281 Apr 28 21:24 app.json
drwx------ 2 u29950 dyno 4.0K Apr 28 21:25 bin
drwx------ 5 u29950 dyno 4.0K Apr 28 21:24 config
-rw------- 1 u29950 dyno 154 Apr 28 21:24 config.ru
drwx------ 3 u29950 dyno 4.0K Apr 28 21:24 db
-rw------- 1 u29950 dyno 9 Apr 28 21:24 .env
-rw------- 1 u29950 dyno 1.1K Apr 28 21:24 Gemfile
-rw------- 1 u29950 dyno 5.8K Apr 28 21:24 Gemfile.lock
drwx------ 2 u29950 dyno 4.0K Apr 28 21:24 .github
-rw------- 1 u29950 dyno 577 Apr 28 21:24 .gitignore
drwx------ 3 u29950 dyno 4.0K Apr 28 21:25 .heroku
drwx------ 4 u29950 dyno 4.0K Apr 28 21:24 lib
drwx------ 2 u29950 dyno 4.0K Apr 28 21:24 log
-rw------- 1 u29950 dyno 40 Apr 28 21:24 Procfile
drwx------ 2 u29950 dyno 4.0K Apr 28 21:25 .profile.d
drwx------ 3 u29950 dyno 4.0K Apr 28 21:25 public
-rw------- 1 u29950 dyno 249 Apr 28 21:24 Rakefile
-rw------- 1 u29950 dyno 3.0K Apr 28 21:24 README.md
drwx------ 8 u29950 dyno 4.0K Apr 28 21:24 test
drwx------ 4 u29950 dyno 4.0K Apr 28 21:25 tmp
drwx------ 6 u29950 dyno 4.0K Apr 28 21:25 vendor
~ $ exit
exit
If you receive an error, Error connecting to process, configure your firewall.
Type exit to exit the shell. You can run any command this way such as rails console or rake db:migrate.
Define Config Vars
Heroku lets you externalize configuration by storing data such as encryption keys or external resource addresses in config vars.
At runtime, we expose config vars as environment variables to the application.
For example, modify app/views/welcome/index.erb so that the method repeats an action depending on the value of the TIMES environment variable. Change the file so that its first few lines read as follows:
<% for i in 0..(ENV['TIMES'] ? ENV['TIMES'].to_i : 2) do %>
<p>Hello World #<%= i %>!</p>
<% end %>
The heroku local command automatically sets up the environment based on the contents of the .env file in your local directory. In the top level directory of your sample project, there’s already a .env file that contains:
TIMES=10
If you run the app with heroku local --port=5006, you see “Hello World” ten times when you refresh your browser.
To set the config var on Heroku, execute the following:
$ heroku config:set TIMES=10
Setting TIMES and restarting shielded-stream-86785... done, v5
TIMES: 10
View the app’s config vars using heroku config:
$ heroku config
TIMES: 10
...
To see this change in action, deploy your changed application to Heroku.
Use a Database
Listing the config vars for your app displays the URL that your app uses to connect to the database, DATABASE_URL:
$ heroku config
DATABASE_URL: postgres://xx:yyy@host:5432/d8slm9t7b5mjnd
HEROKU_POSTGRESQL_BROWN_URL: postgres://xx:yyy@host:5432/d8slm9t7b5mjnd
...
Heroku also provides a pg command that shows a lot more information:
$ heroku pg
=== DATABASE_URL
Plan: essential-0
Status: Available
Connections: unknown/20
PG Version: 17.9
Created: 2026-04-28 21:22
Data Size: unknown usage / 1 GB (In compliance)
Tables: 0/4000 (In compliance)
Fork/Follow: Unsupported
Rollback: Unsupported
Continuous Protection: On
Add-on: postgresql-solid-73058
The example app you deployed already has database functionality. It has a controller and database model for widgets, used by your app’s /widgets page. You can visit the page by appending /widgets to your app’s URL.
If you visit the URL, you see an error page appear. Check out the error message using heroku logs or in Papertrail to see something like this:
PG::UndefinedTable: ERROR: relation "widgets" does not exist
This error indicates that while we could connect to the database, the widgets table wasn’t found. You can fix that error by running rake db:migrate via heroku run:
$ heroku run "rake db:migrate db:migrate:status"
Running rake db:migrate db:migrate:status on shielded-stream-86785... starting, run.6100
Running rake db:migrate db:migrate:status on shielded-stream-86785... connecting, run.6100
Running rake db:migrate db:migrate:status on shielded-stream-86785... up, run.6100
Generating image variants require the image_processing gem. Please add `gem "image_processing", "~> 1.2"` to your Gemfile or set `config.active_storage.variant_processor = :disabled`.
database: d9jgihjd1fea6q
Status Migration ID Migration Name
--------------------------------------------------
up 20140707111715 Create widgets
Now if you visit the /widgets page of your app again, you can list and create widget records.
If you have Postgres installed locally, you can also interact directly with the database. For example, here’s how to connect to the database using psql and execute a query:
$ heroku pg:psql
d8slm9t7b5mjnd=> \x
d8slm9t7b5mjnd=> select * from widgets;
-[ RECORD 1 ]---------------------------
id | 1
name | My Widget
description | It's amazing
stock | 100
...
Read more about Heroku PostgreSQL.
Delete Your App
Remove the app from your account. We only charge you for the resources you used.
This action permanently deletes your application and any add-ons attached to it.
$ heroku apps:destroy
You can confirm that your app is gone with this command:
$ heroku apps --all
Next Steps
You now know how to configure and deploy a Ruby app, view logs, and start a console.
To learn more, see: