# Heroku's classic Python buildpack and the four files it insists on

> heroku-buildpack-python is the classic buildpack that turns a Python repository into a Heroku slug, and it will refuse a directory without a requirements.txt, Pipfile.lock, poetry.lock or uv.lock. It suits teams already deploying to Heroku with the classic buildpack system, and it is the wrong tool for anyone moving to the Cloud Native buildpack model.

**heroku/heroku-buildpack-python** — Heroku's classic buildpack for Python apps

- Repository: https://github.com/heroku/heroku-buildpack-python
- Website: https://devcenter.heroku.com/categories/python-support
- Stars: 993 · Forks: 1,815
- Language: Ruby
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/heroku-heroku-buildpack-python

## The buildpack that turns a repository into a slug

heroku-buildpack-python is the classic Heroku buildpack for Python applications, and it is written in Ruby even though the apps it builds are Python. The build model is the standard Heroku one: the platform hands the buildpack a directory of source, the buildpack detects the stack, compiles dependencies into a slug, and reports the result. The repository is laid out for that contract, with a `bin/` directory holding the lifecycle scripts, a `lib/` directory for the implementation, `builds/` for the compiled output, `requirements/` for pinned build requirements, a `vendor/` directory, and a `spec/` directory for the test suite. The audience is narrow and well defined. Teams that deploy Python apps to Heroku with the classic buildpack system, and who want the platform to pick the interpreter, install the dependencies and produce a web process without a Dockerfile. The project also states that there are no restrictions on what software can be used, as long as it is pip-installable, and it recommends Django and Flask as web frameworks with Gunicorn as the webserver.

## One of four dependency files, in the repository root

The buildpack will not build an application that has no recognised dependency file. The requirement is stated plainly: a `requirements.txt`, `Pipfile.lock`, `poetry.lock` or `uv.lock` file must be present in the root directory of the app source. That list covers the four dependency workflows the classic buildpack supports, from a plain pip requirements file through Pipenv, Poetry and uv. The location matters as much as the name, since a lock file in a subdirectory is not found, and this is the first thing to check when a build fails for no visible reason. There is one extra condition tied to uv: when the package manager is uv, a `.python-version` file is also required, so a uv project carries two files at the root instead of one. The buildpack then produces a web process, and the runtime contract for that process is narrow and non-negotiable: web processes must bind to `$PORT`, and only the HTTP protocol is permitted for incoming connections. Anything that expects to open a socket on a fixed port or to receive raw TCP will not work as it is.

## Where the Python version comes from, in precedence order

The interpreter version is read from your repository, not chosen at deploy time, and the buildpack looks in four places in descending order of precedence.

```bash
1. `runtime.txt` file (deprecated)
2. `.python-version` file (recommended)
3. The `python_full_version` field in the `Pipfile.lock` file
4. The `python_version` field in the `Pipfile.lock` file
```

The recommendation is to create a `.python-version` file in the root directory of the app. The project also recommends the major version form rather than a full patch pin, and explains why in one sentence: it allows the app to receive Python security updates. The example given is a file containing `3.14`, which asks for the latest patch release of Python 3.14. The Pipfile.lock entries are interesting because they mean a Pipenv project can carry its interpreter requirement in the lock file instead of a separate file, and that `python_full_version` wins over `python_version` when both are present. The `runtime.txt` form still works but is marked deprecated.

## The version matrix and the pin you inherit on the first build

The supported interpreter versions are listed explicitly, and the list is short.

```bash
- Python 3.15
- Python 3.14
- Python 3.13
- Python 3.12
- Python 3.11
```

Python 3.10 is described as deprecated on Heroku, and versions older than those listed are no longer supported because they have reached end of life upstream. The current default Python version is 3.14. The behaviour when you specify nothing is the part worth understanding carefully. If none of the four version sources is found, the buildpack uses a default Python version for the first build of an app, and then subsequent builds of that app are pinned to that version, unless the build cache is cleared or a different version is requested. In practice that means an app with no version file does not silently drift: it settles on one interpreter and stays there, and the only ways to move it are to add a version file, clear the cache, or ask for a different version. This is the reason the project advises specifying a version rather than relying on the default.

## Gunicorn, $PORT and the HTTP only rule at runtime

Two runtime rules define the contract between your app and the platform. The web process must bind to `$PORT`, an environment variable supplied by the platform rather than a fixed number, and only the HTTP protocol is permitted for incoming connections. The recommended webserver is Gunicorn, and the recommended frameworks are Django and Flask, though the project is explicit that the choice of software is unrestricted as long as it is pip-installable. The consequence of the `$PORT` rule is the classic one. A development server that binds to 5000 or 8000 works locally and fails on the platform, so the app has to read the port from the environment, and a container or CI image that hardcodes a port will not behave the same way on Heroku. The HTTP only restriction rules out a long lived TCP or websocket server listening on its own socket. Everything else in the classic buildpack model, including the dependency file and the Python version, is under your control through files in the repository, which makes the whole configuration reviewable in a pull request.

## Classic buildpack versus the Cloud Native buildpack

The project itself points at the alternative. A note in the documentation says that this is the Heroku Classic Buildpack for Python and that anyone looking for the Heroku Cloud Native Buildpack for Python may find it at the `heroku/buildpacks-python` repository. The difference in approach is the build interface rather than the Python handling. The classic buildpack is a self-contained Ruby program with its own lifecycle scripts, its own pinned build requirements under `requirements/`, a vendored directory and a `buildpack.toml`, and it resolves dependencies itself from the lock file it recognises. The Cloud Native buildpack is a set of runnable buildpacks that plug into a buildpack lifecycle, where detection and compilation are separate components that other runtimes can share. For an application already on the classic system, the practical difference is that migrating means changing the build contract rather than changing the dependency file, and the classic buildpack's support for `Pipfile.lock`, `poetry.lock` and `uv.lock` is not something a generic lifecycle gives you for free.

## Running the buildpack locally against a Docker stack

The repository is set up so a contributor can exercise the buildpack without deploying. The Makefile defines the stack it targets and a fixture directory used for the test runs.

```bash
STACK ?= heroku-26
FIXTURE ?= spec/fixtures/python_version_unspecified
```

The stack name is converted into a build image tag, and the run target starts a container from that image with the source directory mounted read only, then walks the lifecycle in order: detection, compilation, reporting, and finally release, with an inline buildpack compile step when the fixture provides one.

```bash
STACK_IMAGE_TAG := heroku/$(subst -,:,$(STACK))-build
```

Around that sit the quality targets, which is a good sign about how the project treats shell scripts in a build path.

```bash
lint: lint-scripts check-format lint-ruby
```

The sub targets run shellcheck over the shell files, shfmt in diff mode over the tree, and rubocop over the Ruby code, and there is a separate `check:spell` style target and a `hatchet.json` file with its lock for the integration test harness. The `spec/` directory holds the test suite, and `CONTRIBUTING.md` is the documented entry point for setting up, testing and changing the repository.

## A Ruby project shipping Python apps, with weekly tags

The maintenance picture is clear from the release list. Three tags landed in October 2026, v356 on 2026-10-14, v355 on 2026-10-09 and v354 on 2026-10-02, and the last push was on 2026-10-14. The version numbers are sequential integers rather than semantic versions, which is typical of a buildpack whose identity is the stack it supports rather than an API surface. A `CHANGELOG.md` sits at the top level, along with a `SECURITY.md`, a `CODE_OF_CONDUCT.md` and a `CONTRIBUTING.md`, so the project has the usual set of process documents for a repository that many teams depend on indirectly. The licence is MIT, which permits use inside commercial and closed source deployments without further conditions beyond the licence text. The upgrade cost for an adopter is low in the usual case, since a new tag changes which interpreters or dependency handling the buildpack prefers, and it is worth reading the changelog before a major interpreter change, since the supported version list is where most of the behaviour changes live.

## Conclusion

Adopt heroku-buildpack-python if your apps already deploy to Heroku through the classic buildpack system and you keep one of the four supported dependency lock files in the repository root. Skip it if you are moving to the Cloud Native buildpack model, since the project points you at heroku/buildpacks-python for that, and skip it if you want to choose a Python version at deploy time without a file, because the version is fixed by the repository contents. Verify three things before your next deploy: that your `.python-version` names a major version such as `3.14` rather than a patch, that your dependency file is one of the four the buildpack recognises, and that your web process binds to `$PORT`. Releases v356, v355 and v354 landed in October 2026 and the licence is MIT.

## FAQ

### What files does the Heroku Python buildpack require?

One of four dependency files must sit in the root of the app source: a `requirements.txt`, `Pipfile.lock`, `poetry.lock` or `uv.lock`. Projects using uv also need a `.python-version` file in the root.

### How do I choose the Python version for a Heroku build?

The buildpack looks in four places in order: a `runtime.txt` file, which is deprecated, a `.python-version` file, which is recommended, then the `python_full_version` and `python_version` fields in `Pipfile.lock`. The project recommends the major version form so the app keeps receiving Python security updates.

### Which Python versions are supported on Heroku right now?

Python 3.15, 3.14, 3.13, 3.12 and 3.11 are supported, with 3.14 as the default. Python 3.10 is deprecated on Heroku, and anything older is no longer supported because it has reached end of life upstream.

### Why does my Heroku Python app fail to start with a port error?

Web processes on Heroku must bind to `$PORT` rather than a fixed port, and only the HTTP protocol is permitted for incoming connections. A development server bound to a hardcoded port works locally and fails on the platform.

### How do I run the Heroku Python buildpack locally?

The Makefile has a `run` target that starts a container from the build image for the chosen stack, with `STACK` and `FIXTURE` as overridable variables, and then walks detection, compile, report and release. The `lint` target runs shellcheck, shfmt and rubocop over the scripts and Ruby code.

## Sources

- [heroku/heroku-buildpack-python on GitHub](https://github.com/heroku/heroku-buildpack-python)
- [License: MIT](https://github.com/heroku/heroku-buildpack-python/blob/main/LICENSE)
- [Project website](https://devcenter.heroku.com/categories/python-support)
- [README](https://github.com/heroku/heroku-buildpack-python/blob/main/README.md)
- [Releases](https://github.com/heroku/heroku-buildpack-python/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/heroku-heroku-buildpack-python
