PHP Behavior in Heroku
Last updated December 02, 2024
This article is a work in progress, or documents a feature that is not yet released to all users. This article is unlisted. Only those with the link can access it.
Table of Contents
The Heroku Platform uses the PHP buildpack to handle deployed PHP applications. The following shows Heroku’s behavior towards PHP apps and how it recognizes, runs, and builds them.
Auto-Detection
Heroku PHP support applies to applications that have a file named composer.json
in the root directory. Even if an application has no Composer dependencies, it must include at least an empty ({}
) composer.json
in order to be recognized as a PHP application.
When Heroku recognizes a PHP application, it responds accordingly during a push:
$ git push heroku main
-----> PHP app detected
…
If composer.json
specifies dependencies of any kind in its require
section, the corresponding composer.lock
that gets generated by running composer update
must also be committed to the repository, or the push is rejected. This ensures that the dependencies Heroku installs are the same as in any other environment. For detailed instructions, check out the “Manage Dependencies” section of the Deploying PHP guide.
PHP Runtimes
Heroku allows you to run your application using the official PHP runtime. To learn how to specify a PHP runtime, refer to the corresponding Dev Center article.
Runtime Settings
All PHP runtimes use the respective release’s php.ini-production
file as their base php.ini
configuration.
Notwithstanding the above, the following INI directives are set to Heroku-specific values:
date.timezone
is set toUTC
error_reporting
is set toE_ALL & ~E_STRICT
for PHP versions before 8.1E_ALL & ~E_STRICT & ~E_DEPRECATED
for PHP 8.1 or later
expose_php
is set toOff
session.sid_length
is set to32
(for PHP 7.1 and later)short_open_tag
is set toOn
user_ini.cache_ttl
is set to86400
variables_order
is set toEGPCS
In addition, the PHP runtimes always have OPcache enabled for improved performance, with the following configuration changes optimized for the specific characteristics of Heroku’s dynos:
opcache.enable_cli
is set to1
opcache.validate_timestamps
is set to0
PHP CLI
The memory_limit
PHP INI directive for the php
CLI executable defaults to the full available dyno memory for PHP versions 7.2 or later. For example, worker dynos using a php
command use this memory limit instead of the default PHP INI value of 128M
.
Default Runtime
Applications that don’t use a composer.json
, or where composer.lock
contains no requirements for package php
even in any dependent package, picks the latest possible version of either PHP 7 or PHP 8 on the heroku-20
stack, and PHP 8 on all other stacks.
Upgrades
If you deploy an application that doesn’t declare a runtime version dependency, the then-latest version of PHP is used. Your application upgrades to more recent versions of PHP if available automatically upon the next deployment.
If your application declares runtime version dependencies, the most recent version matching the version constraint will be selected for installation.
Runtime Behavior
The $PATH
environment variable contains all necessary paths for an application to function at runtime. The Composer bin-dir
is appended to $PATH
for convenience.
PHP-FPM Configuration
PHP-FPM is set up to automatically spawn a suitable number of worker processes depending on dyno size and the configured PHP memory_limit
. For more information, see Optimizing PHP Application Concurrency.
Timeouts
When a request reaches the Heroku router’s request timeout, a PHP-FPM process continues to run, potentially as long as it takes for example an external timeout to occur. This would tie up that PHP-FPM process, which could then no longer respond to other incoming requests.
For applications using PHP 7.4 or later, by default, PHP-FPM will therefore
- log a backtrace of requests that take longer than three seconds (
request_slowlog_timeout
directive), and - terminate requests that have exceeded an execution time of 30 seconds (
request_terminate_timeout
directive) and therefore likely timed out.
You may adjust the settings for PHP-FPM to change these (or other) configuration settings.
Composer Configuration
For convenience, the following settings for Composer are automatically set using environment variables:
$COMPOSER_MEMORY_LIMIT
defaults to the available dyno memory;$COMPOSER_MIRROR_PATH_REPOS
defaults to1
;$COMPOSER_NO_INTERACTION
defaults to1
;$COMPOSER_PROCESS_TIMEOUT
defaults to0
.
Customizing Settings
PHP
Any .user.ini
file that’s placed into a project according to the instructions in the PHP manual loads after the main php.ini
. You can use these to set any directive permitted in PHP_INI_ALL
, PHP_INI_USER
and PHP_INI_PERDIR
contexts.
For additional details on this and other ways of customizing settings for the PHP runtime, refer to the corresponding Dev Center article.
Build Behavior
Installation of Dependencies
The following command is run during a deploy to resolve dependencies unless composer.json
is empty and no composer.lock
is present:
$ composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
Heroku won’t install development dependencies from the require-dev
section of composer.json
. However, if the require-dev
section contains a PHP runtime version requirement or lists a dependency that contains such a requirement, then the require
section of composer.json
must also contain a PHP runtime version requirement or list a dependency that contains such a requirement. This is to ensure that Heroku doesn’t select a default PHP runtime version that conflicts with what other environments (which include require-dev
dependencies) install.
The installed version of Composer will be printed for your reference before installation begins. Builds are run using the latest possible Composer version (1.x, 2.2.x LTS, or 2.3+) that’s compatible with an application’s composer.lock
. The respective version of Composer is available on $PATH
at app runtime under the command name composer
.
An application’s Composer cache directory is persisted between builds to speed up package installation on subsequent deploys.
Custom Compile Step
For applications that wish to execute an additional compilation step during a build that shouldn’t be part of a standard post-install-cmd
Composer script, for example an asset compilation or cache pre-warming procedure, a compile
custom command, if present in composer.json
, executes using the following command:
$ composer compile --no-dev --no-interaction
Any such custom script command defined in composer.json can either be a single string, or an array of multiple commands to execute; example:
{
"scripts": {
"compile": [
"@php app/console assetic:dump --env=prod --no-debug",
"MyVendor\\MyClass::postDeployComposerCallback"
]
}
}
If you must execute php
or composer
in any Composer script, always reference the executables using the @php
or @composer
shorthand notations. This will always call the correct PHP or Composer executable in any environment, and also ensure any Composer configuration, including the right PHP memory_limit
, is applied.
Composer’s bin-dir
is pushed on top of $PATH
during command execution, making binaries installed by dependencies easily accessible as CLI commands when writing scripts without having to use vendor/bin/
or a similar prefix.
Private Repositories
To use private repositories like Private Packagist, or packages from a source that requires authentication (such as a private GitHub repository), Composer must be provided with authentication details (typically a token generated by the provider or service).
On a development machine, these are typically stored by Composer in auth.json
, but on Heroku, such secrets are stored as environment variables. The COMPOSER_AUTH
environment variable is automatically read by Composer; its JSON structure is identical to auth.json
.
The following entries are allowed as top-level keys in the JSON document:
Each entry then contains a hash of domains as keys and authentication details as values; the authentication detail structure is specific to each of the sources above and described in the documentation.
When using GitHub Enterprise or the Self-Managed version of GitLab, remember to also set the github-domains
or gitlab-domains
config option inside your project’s composer.json
.
For example, to store authentication details for a Private Packagist, account, set the COMPOSER_AUTH
variable using heroku config:set
with http-basic
details (replacing “YOURTOKEN” with the actual token Private Packagist generated):
$ heroku config:set COMPOSER_AUTH='{"http-basic":{"repo.packagist.com":{"username":"token","password":"YOURTOKEN"}}}'
To give another example, when using code from private GitHub repositories as Composer dependencies, a personal OAuth token can be set for authentication. After creating a new Token, you can set it on Heroku (replacing “YOURTOKEN” with the actual token GitHub generated):
$ heroku config:set COMPOSER_AUTH='{"github-oauth":{"github.com":"YOURTOKEN"}}'
The private repository URL in your composer.json
must use the https://
and not the git://
protocol for Composer to be able to use the OAuth token for authentication.
Several sets of authentication details can also be combined into a single document; for example, to use both private GitHub and private BitBucket repositories:
$ heroku config:set COMPOSER_AUTH='{
"github-oauth": {"github.com": "YOURTOKEN"},
"bitbucket-oauth": {"bitbucket.org": {
"consumer-key": "YOURKEY", "consumer-secret": "YOURSECRET"}
}
}'
You may use line breaks within quotes when setting environment variables on Heroku as shown in the example above, but you must ensure that the quoting is correct when running the heroku config:set
command.
Customer Support
You can submit issues via one of the Heroku Support channels.