Getting Started on Heroku Fir with Python
Introduction
Complete this tutorial to deploy a sample Python Django 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
- Python version 3.10 or newer (ideally 3.13) installed locally - see the installation guides for macOS, Windows, and Linux.
Using dynos to complete this tutorial counts towards your usage. We recommend using 1X-Classic dynos 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:
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 the Preparing a Codebase for Heroku Deployment 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/python-getting-started.git
$ cd python-getting-started
You now have a functioning Git repository that contains a simple Python app, that uses the web framework Django. It includes a .python-version
file specifying the Python version to use and a requirements.txt
, used by Python’s dependency manager, pip.
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: gunicorn --config gunicorn.conf.py gettingstarted.wsgi
This Procfile declares a process type web
and the command needed to run it. The name web
is important here because it declares that this process type attaches to Heroku’s HTTP routing stack and receives web traffic when deployed. The command used here runs Gunicorn, 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.
Microsoft Windows
The sample app has an additional Procfile
for local development on Microsoft Windows, located in the file Procfile.windows
. Later tutorial steps use this instead to start a different web server compatible with Windows.
web: python manage.py runserver %PORT%
IPv6 Compatibility
The code we’re deploying is already ready for IPv6, but when you’re deploying your own app you must make sure it binds to the IPv6 interface.
Fir uses IPv6 to route web requests. By default, Gunicorn tries to bind to the IPv4 interface 0.0.0.0
when the PORT
env var is set. Your app must instead bind to the IPv6 interface ::
.
To accomplish this, use Gunicorn’s bind
setting in gunicorn.conf.py
:
bind = ["[::]:{}".format(os.environ.get("PORT", 5006))]
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, pure-river-35806
http://pure-river-35806-0e63ebe22cab.herokuapp.com/ | https://git.heroku.com/pure-river-35806.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, pure-river-35806
. You can specify your own app name.
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 30 paths from c92af06
remote: Compressing source files... done.
remote: Building source:
remote: Extracting source
remote: Image with name "pure-river-35806/builds" not found
remote: 2 of 3 buildpacks participating
remote: heroku/python 0.22.0
remote: heroku/procfile 3.2.0
remote:
remote: [Determining Python version]
remote: Using Python version 3.13 specified in .python-version
remote:
remote: [Installing Python]
remote: Installing Python 3.13.1
remote:
remote: [Installing pip]
remote: Installing pip 24.3.1
remote:
remote: [Installing dependencies using pip]
remote: Creating virtual environment
remote: Running 'pip install -r requirements.txt'
remote: Collecting django<5.2,>=5.1 (from -r requirements.txt (line 1))
remote: Downloading Django-5.1.4-py3-none-any.whl.metadata (4.2 kB)
remote: Collecting gunicorn<24,>=23 (from -r requirements.txt (line 2))
remote: Downloading gunicorn-23.0.0-py3-none-any.whl.metadata (4.4 kB)
remote: Collecting dj-database-url<3,>=2 (from -r requirements.txt (line 3))
remote: Downloading dj_database_url-2.3.0-py3-none-any.whl.metadata (12 kB)
remote: Collecting whitenoise<7,>=6 (from whitenoise[brotli]<7,>=6->-r requirements.txt (line 4))
remote: Downloading whitenoise-6.8.2-py3-none-any.whl.metadata (3.6 kB)
remote: Collecting asgiref<4,>=3.8.1 (from django<5.2,>=5.1->-r requirements.txt (line 1))
remote: Downloading asgiref-3.8.1-py3-none-any.whl.metadata (9.3 kB)
remote: Collecting sqlparse>=0.3.1 (from django<5.2,>=5.1->-r requirements.txt (line 1))
remote: Downloading sqlparse-0.5.3-py3-none-any.whl.metadata (3.9 kB)
remote: Collecting packaging (from gunicorn<24,>=23->-r requirements.txt (line 2))
remote: Downloading packaging-24.2-py3-none-any.whl.metadata (3.2 kB)
remote: Collecting typing-extensions>=3.10.0.0 (from dj-database-url<3,>=2->-r requirements.txt (line 3))
remote: Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
remote: Collecting brotli (from whitenoise[brotli]<7,>=6->-r requirements.txt (line 4))
remote: Downloading Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.metadata (5.5 kB)
remote: Downloading Django-5.1.4-py3-none-any.whl (8.3 MB)
remote: Downloading gunicorn-23.0.0-py3-none-any.whl (85 kB)
remote: Downloading dj_database_url-2.3.0-py3-none-any.whl (7.8 kB)
remote: Downloading whitenoise-6.8.2-py3-none-any.whl (20 kB)
remote: Downloading asgiref-3.8.1-py3-none-any.whl (23 kB)
remote: Downloading sqlparse-0.5.3-py3-none-any.whl (44 kB)
remote: Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB)
remote: Downloading Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (2.9 MB)
remote: Downloading packaging-24.2-py3-none-any.whl (65 kB)
remote: Installing collected packages: brotli, whitenoise, typing-extensions, sqlparse, packaging, asgiref, gunicorn, django, dj-database-url
remote: Successfully installed asgiref-3.8.1 brotli-1.1.0 dj-database-url-2.3.0 django-5.1.4 gunicorn-23.0.0 packaging-24.2 sqlparse-0.5.3 typing-extensions-4.12.2 whitenoise-6.8.2
remote:
remote: [Generating Django static files]
remote: Running 'manage.py collectstatic'
remote:
remote: 1 static file symlinked to '/workspace/staticfiles', 1 post-processed.
remote:
remote: ## Procfile Buildpack
remote:
remote: - Processes from `Procfile`
remote: - web: `gunicorn --config gunicorn.conf.py gettingstarted.wsgi`
remote: - Done (finished in < 0.1s)
remote: Adding layer 'heroku/python:python'
remote: Adding layer 'heroku/python:venv'
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 pure-river-35806/builds...
remote: *** Images (sha256:44737045e2f3e256c9cae42b05f6ec2eac8e0e4836b6be53867e6eb51e332ffa):
remote: pure-river-35806/builds:bb46c6e1-b917-45db-96c8-257af2476205
remote: Adding cache layer 'heroku/python:pip'
remote: Adding cache layer 'heroku/python:pip-cache'
remote: Adding cache layer 'heroku/python:python'
remote: Uploading cache
remote: Launching...
remote: http://pure-river-35806-0e63ebe22cab.herokuapp.com/ deployed to Heroku
remote: Verifying deploy... done.
To https://git.heroku.com/pure-river-35806.git
* [new branch] main -> main
The app is now deployed. The default dyno size for Fir Private Spaces is 1X-Classic.
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-01-10T13:29:14.855876+00:00 heroku-router[web]: at=info method=GET path="/" host=pure-river-35806-0e63ebe22cab.herokuapp.com request_id=4af681f2-0c61-28bb-95c5-4baf475ecf82 fwd="123.456.789.0" dyno=web-6469b7d76d-7w5sd connect=0ms service=1ms status=200 bytes=9585 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
.
Install App Dependencies Locally
Heroku recognizes an app as a Python app by looking for key files. Including a requirements.txt
in the root directory is one way we recognize your Python app.
The demo app you deployed already has a requirements.txt
:
django>=5.1,<5.2
gunicorn>=23,<24
dj-database-url>=2,<3
whitenoise[brotli]>=6,<7
The requirements.txt
file lists the app’s dependencies. When you deploy an app on Heroku, the Python buildpack installs these dependencies using the pip install
command.
To run the app locally, you must also install dependencies locally.
Before you do this, you must create and activate a virtual environment, also known as a venv
. This environment lets you install the packages without affecting your system Python installation.
First, check that your local Python version:
$ python3 --version
If your Python version is older than 3.10 you must install a newer version of Python before creating a virtual environment. See the Python installation guides for macOS, Windows, and Linux.
Create a virtual environment inside the example app directory with this command:
$ python3 -m venv --upgrade-deps .venv
Next, activate the virtual environment:
If you’re on a Microsoft Windows system, activate with:
.\.venv\Scripts\activate
Or if you’re on a macOS/Linux system, activate with:
$ source .venv/bin/activate
For help with setting up a virtual environment, see the Python documentation.
Finally, install the dependencies into the newly created environment:
$ pip install -r requirements.txt
Installing the dependencies also installs their dependencies. View all installed packages using pip list
:
$ pip list
Package Version
----------------- -------
asgiref 3.8.1
Brotli 1.1.0
dj-database-url 2.3.0
Django 5.1.4
gunicorn 23.0.0
packaging 24.2
pip 24.3.1
sqlparse 0.5.3
typing_extensions 4.12.2
whitenoise 6.8.2
After installing dependencies, you can run your app locally.
Run the App Locally
Start your application locally using the heroku local
command, which is a part of the Heroku CLI.
If you’re on a Microsoft Windows system, run this:
$ heroku local --port 5006 -f Procfile.windows
Or if you’re on a macOS/Linux system, use the default Procfile
by running:
$ heroku local --port 5006
Running the command starts up the local web server:
[OKAY] Loaded ENV .env File as KEY=VALUE Format
13:29:21 web.1 | [2025-01-10 13:29:21 +0000] [52126] [INFO] Starting gunicorn 23.0.0
13:29:21 web.1 | [2025-01-10 13:29:21 +0000] [52126] [INFO] Listening at: http://[::]:5006 (52126)
13:29:21 web.1 | [2025-01-10 13:29:21 +0000] [52126] [INFO] Using worker: gthread
13:29:21 web.1 | [2025-01-10 13:29:21 +0000] [52127] [INFO] Booting worker with pid: 52127
If you see an error when running heroku local
, check that you have installed the app’s dependencies locally and the virtual environment is still activated.
Just like Heroku, heroku local
uses the Procfile
to know what command to execute.
Open http://localhost:5006 with your web browser. You should see your app running locally.
To stop the app from running locally, go back to your terminal window and press Ctrl
+C
to exit.
Push Local Changes
In this step, you propagate a local change to the application to Heroku.
Modify requirements.txt
to include an additional dependency on the requests
package.
In file requirements.txt
, on line 5 add:
requests
The file now looks like this:
django>=5.1,<5.2
gunicorn>=23,<24
dj-database-url>=2,<3
whitenoise[brotli]>=6,<7
requests
Use pip to install the requests
package via the updated requirements.txt
file:
$ pip install -r requirements.txt
Modify hello/views.py
to import the requests
module and the Django HttpResponse
class at the top of the file.
In file hello/views.py
, on line 1 add:
import requests
from django.http import HttpResponse
Now modify the index
method to use the module. Try replacing the current index
method with the following code:
def index(request):
r = requests.get('https://httpbin.org/status/418', timeout=10)
return HttpResponse('<pre>' + r.text + '</pre>')
Now test again locally.
If you’re on a Microsoft Windows system, run this:
$ heroku local --port 5006 -f Procfile.windows
Or if you’re on a macOS/Linux system, use the default Procfile
by running:
$ heroku local --port 5006
Visit your application at http://localhost:5006. If your changes worked, you will see the output of fetching https://httpbin.org/status/418:
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
If you see the error Internal Server Error
in your browser, and the error ModuleNotFoundError: No module named 'requests'
in the terminal log output, check that the requests
package installed successfully.
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 "Updated index view"
[main e3d34d6] Updated index view
2 files changed, 3 insertions(+)
Now deploy as before:
$ git push heroku main
Finally, check that everything is working:
$ heroku open
Debugging
The Heroku Python 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 Python CNB. If you’re interested, check out the Python CNB tutorial.
Start a Console
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 (1X-Classic): gunicorn --config gunicorn.conf.py gettingstarted.wsgi (1)
web-6469b7d76d-7w5sd: up 2025/01/10 13:29:10 +0000 (~ 13s ago)
Use that dyno name to run an interactive bash
session:
$ heroku run:inside web-6469b7d76d-7w5sd "bash"
Running launcher bash on pure-river-35806...
Running launcher bash on pure-river-35806... up, web-6469b7d76d-7w5sd
heroku@web-6469b7d76d-7w5sd:/workspace$ ls -lah
total 40K
drwxrwsrwx. 1 heroku heroku 60 Jan 10 13:29 .
drwxr-xr-x. 1 root root 45 Jan 10 13:29 ..
-rw-r--r--. 1 heroku heroku 273 Jan 1 1980 .env
drwxr-xr-x. 2 heroku heroku 78 Jan 1 1980 .github
-rw-r--r--. 1 heroku heroku 106 Jan 1 1980 .gitignore
-rw-r--r--. 1 heroku heroku 5 Jan 1 1980 .python-version
-rw-r--r--. 1 heroku heroku 388 Jan 1 1980 Procfile
-rw-r--r--. 1 heroku heroku 39 Jan 1 1980 Procfile.windows
-rw-r--r--. 1 heroku heroku 2.3K Jan 1 1980 README.md
drwxr-sr-x. 2 heroku heroku 43 Jan 10 13:29 __pycache__
-rw-r--r--. 1 heroku heroku 574 Jan 1 1980 app.json
drwxr-xr-x. 1 heroku heroku 25 Jan 1 1980 gettingstarted
-rw-r--r--. 1 heroku heroku 3.4K Jan 1 1980 gunicorn.conf.py
drwxr-xr-x. 1 heroku heroku 25 Jan 1 1980 hello
-rwxr-xr-x. 1 heroku heroku 671 Jan 1 1980 manage.py
-rw-r--r--. 1 heroku heroku 586 Jan 1 1980 requirements.txt
drwxr-sr-x. 2 heroku heroku 64 Jan 1 1980 staticfiles
heroku@web-6469b7d76d-7w5sd:/workspace$ exit
exit
If you receive an error, Error connecting to process
, configure your firewall.
Type exit
to exit the shell and terminate the dyno.
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.
To demonstrate this, modify hello/views.py
so that the method repeats an action depending on the value of the TIMES
environment variable.
In file hello/views.py
, on line 1 add:
import os
Now modify the index
method so that it repeats the word Hello!
the number of times specified in the TIMES
environment variable:
def index(request):
times = int(os.environ.get('TIMES', 3))
return HttpResponse('Hello! ' * times)
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=2
Run the app with heroku local --port 5006
and visit http://localhost:5006 to see “Hello!” twice.
To set the config var on Heroku, execute the following:
$ heroku config:set TIMES=2
Setting TIMES and restarting pure-river-35806...
Setting TIMES and restarting pure-river-35806... done, v4
TIMES: 2
View the app’s config vars using heroku config
:
$ heroku config
TIMES: 2
...
To see this change in action, deploy your changed application to Heroku.
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 Python app, view logs, and start a console.
To learn more, see: