Deep-dive on the Next Gen Platform. Join the Webinar!

Skip Navigation
Show nav
Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up

Getting Started on Heroku Fir with Scala and Play

Introduction

Complete this tutorial to deploy a sample Scala app to Heroku Private Spaces on the Fir generation of the platform. To deploy the app to the Common Runtime or Cedar Private Spaces, follow this guide instead.

The tutorial assumes that you have:

  • A verified Heroku Account
  • An existing Fir Private Space
  • A team admin or member role that has the app creation permission on the space.
  • An SSH key added to your Heroku account
  • OpenJDK 17 (or newer) installed locally
  • sbt installed
  • Postgres installed locally

Using dynos and databases to complete this tutorial counts towards your usage. We recommend using dyno-1c-0.5gb dynos and an Essential-0 Postgres database to complete this tutorial. Delete all resources after completing the tutorial.

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:

  • Git installation
  • First-time Git setup

Download and run the installer for your platform:

apple logomacOS

Install Homebrew and run:

$ brew install heroku/brew/heroku

windows logoWindows

Download the appropriate installer for your Windows installation:

64-bit installer

32-bit installer

You can find more installation options for the Heroku CLI here.

After installation, you can use the heroku command from your command shell.

On Windows, start the Command Prompt (cmd.exe) or Powershell to access the 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/scala-getting-started
$ cd scala-getting-started

You now have a functioning Git repository that contains a simple application. It includes a build.sbt file, which is used by sbt, a Scala build tool.

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: target/universal/stage/bin/scala-getting-started -Dhttp.port=${PORT}

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.

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 in a Fir Space

Delete your app and database as soon as you’re done to control costs.

 

You can get a list of all Heroku spaces by running $ heroku spaces

Create an app on Heroku to prepare the platform to receive your source code by replacing <space-name> with the name of your Fir space in the command below:

$ heroku create --space <space-name>
Creating app in space <space name>...
Creating app in space <space name>... done, secure-sands-75859
http://secure-sands-75859-f47983ad0d95.redbud-virginia.herokuapp.com/ | https://git.heroku.com/secure-sands-75859.git

When you create an app, a Git remote called heroku also gets created and associated with your local Git repository. Git remotes are versions of your repository that live on other servers. You deploy your app by pushing its code to that special Heroku-hosted remote associated with your app.

Heroku generates a random name for your app, in this case, secure-sands-75859. 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 secure-sands-75859...
Creating heroku-postgresql:essential-0 on secure-sands-75859... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-cubic-39629 is being created in the background. The app will restart when complete...
Use heroku addons:info postgresql-cubic-39629 to check creation progress
Use heroku addons:docs heroku-postgresql to view documentation

You can wait for the database to provision by running this command:

$ heroku pg:wait
Waiting for database postgresql-cubic-39629... Provisioning
Waiting for database postgresql-cubic-39629... Available

After that command exits, your Heroku app can access the Postgres database. The DATABASE_URL environment variable stores your credentials. For applications that use the JVM an additional environment variable JDBC_DATABASE_URL is available. It contains a JDBC compatible connection string. You can see all the add-ons provisioned with the addons command:

$ heroku addons

 Add-on                                     Plan        Price        Max price State
 ────────────────────────────────────────── ─────────── ──────────── ───────── ───────
 heroku-postgresql (postgresql-cubic-39629) essential-0 ~$0.007/hour $5/month  created
  └─ as DATABASE

The table above shows add-ons and the attachments to the current app (secure-sands-75859) 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 28 paths from 02fccba
remote: Compressing source files... done.
remote: Building source:
remote: Heroku Labs: Build Time Config Vars are *not enabled*.
remote: By default, your config vars are only available at runtime. See https://devcenter.heroku.com/build-time-config-vars for details on Build Time Config Vars.
remote: Extracting source
remote: Image with name "secure-sands-75859/builds" not found
remote: 3 of 4 buildpacks participating
remote: heroku/jvm      6.2.1
remote: heroku/sbt      6.2.1
remote: heroku/procfile 4.2.1
remote:
remote: ## Heroku OpenJDK Buildpack
remote:
remote: - OpenJDK version resolution
remote:   - Using version string provided in `system.properties`
remote:   - Selected major version `17` resolves to `17.0.15`
remote: - OpenJDK Installation
remote:   - Downloading and unpacking OpenJDK distribution
remote:   - Done (2.4s)
remote: - Applying JDK overlay
remote:   - Skipping (directory `.jdk-overlay` not present)
remote: - Linking base image certificates as OpenJDK keystore
remote:   - Done
remote: - Done (finished in 2.4s)
remote:
remote: ## Heroku sbt Buildpack
remote:
remote: - Running sbt build
remote:   - Running `sbt compile stage`
remote:
remote:       Downloading sbt launcher for 1.7.2:
remote:         From  https://repo1.maven.org/maven2/org/scala-sbt/sbt-launch/1.7.2/sbt-launch-1.7.2.jar
remote:           To  /layers/heroku_sbt/sbt-extras/launchers/1.7.2/sbt-launch.jar
remote:       Downloading sbt launcher 1.7.2 md5 hash:
remote:         From  https://repo1.maven.org/maven2/org/scala-sbt/sbt-launch/1.7.2/sbt-launch-1.7.2.jar.md5
remote:           To  /layers/heroku_sbt/sbt-extras/launchers/1.7.2/sbt-launch.jar.md5
remote:       /layers/heroku_sbt/sbt-extras/launchers/1.7.2/sbt-launch.jar: OK
remote:       Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
remote:       [info] [launcher] getting org.scala-sbt sbt 1.7.2  (this may take some time)...
remote:       [info] [launcher] getting Scala 2.12.16 (for sbt)...
remote:       [info] welcome to sbt 1.7.2 (Azul Systems, Inc. Java 17.0.15)
remote:       [info] loading global plugins from /layers/heroku_sbt/sbt-global/plugins
remote:       [info] compiling 1 Scala source to /layers/heroku_sbt/sbt-global/plugins/target/scala-2.12/sbt-1.0/classes ...
remote:       [info] Non-compiled module 'compiler-bridge_2.12' for Scala 2.12.16. Compiling...
remote:       [info]   Compilation completed in 14.622s.
remote:       [info] done compiling
remote:       [info] loading settings for project workspace-build from plugins.sbt ...
remote:       [info] loading project definition from /workspace/project
remote:       [info] loading settings for project root from build.sbt ...
remote:       [info]   __              __
remote:       [info]   \ \     ____   / /____ _ __  __
remote:       [info]    \ \   / __ \ / // __ `// / / /
remote:       [info]    / /  / /_/ // // /_/ // /_/ /
remote:       [info]   /_/  / .___//_/ \__,_/ \__, /
remote:       [info]       /_/               /____/
remote:       [info]
remote:       [info] Version 2.8.19 running Java 17.0.15
remote:       [info]
remote:       [info] Play is run entirely by the community. Please consider contributing and/or donating:
remote:       [info] https://www.playframework.com/sponsors
remote:       [info]
remote:       [info] Running Play on Java 17 is experimental. Tweaks are necessary:
remote:       [info] https://github.com/playframework/playframework/releases/2.8.15
remote:       [info]
remote:       [info] compiling 8 Scala sources and 1 Java source to /workspace/target/scala-2.13/classes ...
remote:       [info] Non-compiled module 'compiler-bridge_2.13' for Scala 2.13.10. Compiling...
remote:       [info]   Compilation completed in 13.126s.
remote:       [info] done compiling
remote:       [success] Total time: 20 s, completed Apr 28, 2025, 11:20:22 PM
remote:       [info] Wrote /workspace/target/scala-2.13/scala-getting-started_2.13-1.0-SNAPSHOT.pom
remote:       [success] Total time: 1 s, completed Apr 28, 2025, 11:20:23 PM
remote:
remote:   - Done (1m 3s)
remote: - Done (finished in 1m 3s)
remote:
remote: ## Procfile Buildpack
remote:
remote: - Processes from `Procfile`
remote:   - web: `target/universal/stage/bin/scala-getting-started -Dhttp.port=${PORT}`
remote: - Done (finished in < 0.1s)
remote: Adding layer 'heroku/jvm:openjdk'
remote: Adding layer 'heroku/jvm:runtime'
remote: Adding layer 'buildpacksio/lifecycle:launch.sbom'
remote: Added 1/1 app layer(s)
remote: Adding layer 'buildpacksio/lifecycle:launcher'
remote: Adding layer 'buildpacksio/lifecycle:config'
remote: Adding layer 'buildpacksio/lifecycle:process-types'
remote: Adding label 'io.buildpacks.lifecycle.metadata'
remote: Adding label 'io.buildpacks.build.metadata'
remote: Adding label 'io.buildpacks.project.metadata'
remote: Setting default process type 'web'
remote: Saving secure-sands-75859/builds...
remote: *** Images (sha256:6b744c67eccc273a25ceda059e0a13299717a8df48fa68ced0f48f38b5097a89):
remote:       secure-sands-75859/builds:23928c65-c45a-4b1c-b1c1-4ce6287e392d
remote: Adding cache layer 'heroku/jvm:openjdk'
remote: Adding cache layer 'heroku/sbt:coursier-home'
remote: Adding cache layer 'heroku/sbt:ivy-home'
remote: Adding cache layer 'heroku/sbt:sbt-boot'
remote: Adding cache layer 'heroku/sbt:sbt-extras'
remote: Uploading cache
remote: Launching...
remote: https://secure-sands-75859-f47983ad0d95.redbud-virginia.herokuapp.com/ deployed to Heroku
remote: Verifying deploy... done.
To https://git.heroku.com/secure-sands-75859.git
 * [new branch]      main -> main

The app is now deployed. The default dyno size for Fir Private Spaces is dyno-1c-0.5gb.

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

Fir apps do not retain log history like Cedar apps. To view an event in your Fir logs, you must run the logging command while that event occurs.

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
Fetching logs...

2025-04-28T23:23:01.710930+00:00 heroku-router[web]: at=info method=GET path="/" host=secure-sands-75859-f47983ad0d95.redbud-virginia.herokuapp.com request_id=e31148f5-a26a-cacc-656e-d440f4bc6c4f fwd="123.456.789.0" dyno=web-6495768549-md9tc connect=0ms service=7ms status=200 bytes=10156 protocol=http tls_version=tls1.3

To generate more log messages, refresh the app in your browser.

To stop streaming the logs, press Ctrl+C.

Declare App Dependencies

Heroku recognizes an app as a Scala app by the existence of a build.sbt file in the root directory.

The demo app you deployed already has a build.sbt:

name := """scala-getting-started"""
organization := "com.heroku"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.13.10"

libraryDependencies += guice
libraryDependencies += jdbc
...

The build.sbt file specifies the dependencies to install with your application.

Run sbt stage in your local directory to install the dependencies, preparing your system for running the app locally:

$ sbt stage
[info] welcome to sbt 1.7.2 (Azul Systems, Inc. Java 17.0.13)
[info] loading settings for project scala-getting-started-build from plugins.sbt ...
[info] loading project definition from ./scala-getting-started/project
[info] loading settings for project root from build.sbt ...
[info]   __              __

Push Local Changes

In this step, you propagate a local change to the application to Heroku.

Modify build.sbt to include an additional dependency for the jscience to libraryDependencies.

In file build.sbt, on line 16 add:

  "org.jscience" % "jscience" % "4.3.1",

Add the import statements for the library.

In file app/controllers/Application.scala, on line 2 add:

import org.jscience.physics.amount.Amount
import org.jscience.physics.model.RelativisticModel
import javax.measure.unit.SI

Add a new convert method.

In file app/controllers/Application.scala, on line 13 add:

def convert(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
    RelativisticModel.select()
    val energy = Amount.valueOf("12 GeV")

    Ok("E=mc^2: " + energy + " = " + energy.to(SI.KILOGRAM))
}

Add a new route to the new action.

In file conf/routes, on line 7 add:

GET     /convert                    controllers.Application.convert()

Now test locally:

$ sbt stage
$ heroku local --port=5006

Visit your application’s /convert path at http://localhost:5006/convert. If your changes worked, you see scientific conversions:

E=mc^2: 12 GeV = (2.139194076302506E-26 ± 1.4E-42) kg

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 "Add convert endpoint"
[main 3fe65c5] Add convert endpoint
 4 files changed, 36 insertions(+)
 create mode 100644 install.txt

Now deploy as before:

$ git push heroku main

Finally, check that everything is working:

$ heroku open /convert

Debugging

The Heroku Scala Cloud Native Buildpack (CNB) turns your code into an Open Container Initiative (OCI) container image when you deploy to Fir. This image gets executed on our dynos.

You can use this image locally to reproduce and debug deployment problems. Build an OCI image from your application to debug locally by using the Heroku Scala CNB. If you’re interested, check out the Scala CNB tutorial.

Start a One-off Dyno

The heroku run command to launch an interactive one-off dyno is unavailable for Fir. As an alternative, use heroku run:inside to access a running dyno until we add heroku run for Fir.

You must add an SSH key to your Heroku account before running this command.

To execute the command you need the name of a currently running process. You can see a list with heroku ps:

$ heroku ps
=== web (dyno-1c-0.5gb): target/universal/stage/bin/scala-getting-started -Dhttp.port=${PORT} (1)

web-6495768549-md9tc: up 2025/04/28 18:22:41 -0500 (~ 47s ago)

Use that dyno name to run a command like java -version:

$ heroku run:inside web-6495768549-md9tc "java -version"
Running launcher java -version on ⬢ secure-sands-75859... up, web-6495768549-md9tc
Picked up JAVA_TOOL_OPTIONS: -Xmx300m -Xss512k -XX:CICompilerCount=2 -Dfile.encoding=UTF-8
openjdk version "17.0.12" 2024-07-16 LTS
OpenJDK Runtime Environment Zulu17.52+17-CA (build 17.0.12+7-LTS)
OpenJDK 64-Bit Server VM Zulu17.52+17-CA (build 17.0.12+7-LTS, mixed mode, sharing)

If you receive an error, Error connecting to process, configure your firewall.

Let’s try another example. Create another one-off process within your running dyno and run the bash command to open up a shell on that dyno. You can then execute commands there.

$ heroku run:inside web-6495768549-md9tc "bash"
Running launcher bash on secure-sands-75859...
Running launcher bash on secure-sands-75859... up, web-6495768549-md9tc
heroku@web-6495768549-md9tc:/workspace$ ls -lah
total 40K
drwxrwsrwx. 1 heroku heroku   32 Apr 28 23:22 .
drwxr-xr-x. 1 root   root     45 Apr 28 23:22 ..
-rw-r--r--. 1 heroku heroku   14 Jan  1  1980 .env
drwxr-xr-x. 3 heroku heroku   18 Jan  1  1980 .g8
drwxr-xr-x. 2 heroku heroku   24 Jan  1  1980 .github
-rw-r--r--. 1 heroku heroku   86 Jan  1  1980 .gitignore
-rw-r--r--. 1 heroku heroku 1.1K Jan  1  1980 LICENSE
-rw-r--r--. 1 heroku heroku   74 Jan  1  1980 Procfile
-rw-r--r--. 1 heroku heroku   78 Jan  1  1980 Procfile.windows
-rw-r--r--. 1 heroku heroku 2.3K Jan  1  1980 README.md
drwxr-xr-x. 4 heroku heroku   38 Jan  1  1980 app
-rw-r--r--. 1 heroku heroku  157 Jan  1  1980 app.json
-rw-r--r--. 1 heroku heroku  758 Jan  1  1980 build.sbt
-rw-r--r--. 1 heroku heroku  240 Jan  1  1980 build.sc
drwxr-xr-x. 2 heroku heroku   79 Jan  1  1980 conf
drwxr-sr-x. 2 heroku heroku   29 Apr 28 23:22 logs
drwxr-xr-x. 4 heroku heroku   78 Jan  1  1980 project
drwxr-xr-x. 4 heroku heroku   39 Jan  1  1980 public
-rw-r--r--. 1 heroku heroku  103 Jan  1  1980 system.properties
drwxr-sr-x. 1 heroku heroku   23 Jan  1  1980 target
heroku@web-6495768549-md9tc:/workspace$ exit
exit

Type exit to exit the shell.

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.

Config vars for Fir-generation apps are only available at runtime by default. You can enable Build Time Config Vars to allow for both build and runtime availability.

For example, modify Application.scala so that the method obtains an energy value from the ENERGY environment variable:

def convert(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
    RelativisticModel.select()

    Ok(sys.env.get("ENERGY")
            .map(Amount.valueOf)
            .map(energy => "E=mc^2: " + energy + " = " + energy.to(SI.KILOGRAM))
            .getOrElse("ENERGY environment variable is not set!"))
}

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:

ENERGY=20 GeV

Rebuild the app with sbt stage. Then run the app with heroku local --port=5006 and visit http://localhost:5006/convert to see the conversion value for 20 GeV.

To set the config var on Heroku, execute the following:

$ heroku config:set ENERGY="20 GeV"
Setting ENERGY and restarting secure-sands-75859...
Setting ENERGY and restarting secure-sands-75859... done, v5
ENERGY: 20 GeV

View the app’s config vars using heroku config:

$ heroku config
ENERGY: 20 GeV
...

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:            16.4
Created:               2025-04-28 23:17
Data Size:             unknown usage / 1 GB (In compliance)
Tables:                0/4000 (In compliance)
Fork/Follow:           Unsupported
Rollback:              Unsupported
Continuous Protection: Off
Add-on:                postgresql-cubic-39629

The example app you deployed already has database functionality. You can visit the page by appending /database to your app’s URL.

Database Output

* Read from DB: 2024-11-27 13:07:53.002632
* Read from DB: 2024-11-27 13:07:54.965283
* Read from DB: 2024-11-27 13:07:55.620596

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 -c "SELECT * FROM ticks"
--> Connecting to postgresql-cubic-39629
            tick
----------------------------
 2025-04-28 23:26:16.930214
 2025-04-28 23:26:18.585494
 2025-04-28 23:26:20.212581
(3 rows)

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 Scala app, view logs, and start a console.

To learn more, see:

  • How Heroku Works
  • Preparing a Codebase for Heroku Deployment
  • Heroku Scala Documentation

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • © 2025 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices