Getting Started on Heroku with PHP
Introduction
Complete this tutorial to deploy a sample PHP 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)
- PHP version 8 installed locally - see the Installation and Configuration section of the PHP Manual
- Composer installed locally - see the Getting Started section of the Composer documentation
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 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/php-getting-started.git
$ cd php-getting-started
You now have a functioning Git repository with a simple PHP app that uses the Slim micro framework. It includes a composer.json file that specifies the necessary dependencies, as well as a version range for the PHP language runtime.
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, pure-fjord-27616
https://pure-fjord-27616-945def8d447f.herokuapp.com/ | https://git.heroku.com/pure-fjord-27616.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, pure-fjord-27616. 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 23 paths from 531aee6
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: -----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Preparing platform package installation...
remote: -----> Installing platform packages...
remote: - php (8.5.5)
remote: - apache (2.4.66+heroku1)
remote: - composer (2.9.7)
remote: - nginx (1.30.0)
remote: -----> Installing dependencies...
remote: Installing dependencies from lock file
remote: Verifying lock file contents can be installed on current platform.
remote: Package operations: 22 installs, 0 updates, 0 removals
remote: - Downloading laravel/serializable-closure (v2.0.10)
remote: - Downloading psr/log (3.0.2)
remote: - Downloading monolog/monolog (3.10.0)
remote: - Downloading nikic/fast-route (v1.3.0)
remote: - Downloading psr/http-message (2.0)
remote: - Downloading psr/http-server-handler (1.0.2)
remote: - Downloading psr/http-server-middleware (1.0.2)
remote: - Downloading psr/http-factory (1.1.0)
remote: - Downloading psr/container (2.0.2)
remote: - Downloading slim/slim (4.15.1)
remote: - Downloading php-di/invoker (2.3.7)
remote: - Downloading php-di/php-di (7.1.1)
remote: - Downloading php-di/slim-bridge (3.4.1)
remote: - Downloading ralouphie/getallheaders (3.0.3)
remote: - Downloading fig/http-message-util (1.1.5)
remote: - Downloading slim/psr7 (1.8.0)
remote: - Downloading symfony/polyfill-mbstring (v1.33.0)
remote: - Downloading symfony/polyfill-ctype (v1.33.0)
remote: - Downloading symfony/deprecation-contracts (v3.6.0)
remote: - Downloading twig/twig (v3.23.0)
remote: - Downloading symfony/polyfill-php81 (v1.33.0)
remote: - Downloading slim/twig-view (3.4.1)
remote: - Installing laravel/serializable-closure (v2.0.10): Extracting archive
remote: - Installing psr/log (3.0.2): Extracting archive
remote: - Installing monolog/monolog (3.10.0): Extracting archive
remote: - Installing nikic/fast-route (v1.3.0): Extracting archive
remote: - Installing psr/http-message (2.0): Extracting archive
remote: - Installing psr/http-server-handler (1.0.2): Extracting archive
remote: - Installing psr/http-server-middleware (1.0.2): Extracting archive
remote: - Installing psr/http-factory (1.1.0): Extracting archive
remote: - Installing psr/container (2.0.2): Extracting archive
remote: - Installing slim/slim (4.15.1): Extracting archive
remote: - Installing php-di/invoker (2.3.7): Extracting archive
remote: - Installing php-di/php-di (7.1.1): Extracting archive
remote: - Installing php-di/slim-bridge (3.4.1): Extracting archive
remote: - Installing ralouphie/getallheaders (3.0.3): Extracting archive
remote: - Installing fig/http-message-util (1.1.5): Extracting archive
remote: - Installing slim/psr7 (1.8.0): Extracting archive
remote: - Installing symfony/polyfill-mbstring (v1.33.0): Extracting archive
remote: - Installing symfony/polyfill-ctype (v1.33.0): Extracting archive
remote: - Installing symfony/deprecation-contracts (v3.6.0): Extracting archive
remote: - Installing twig/twig (v3.23.0): Extracting archive
remote: - Installing symfony/polyfill-php81 (v1.33.0): Extracting archive
remote: - Installing slim/twig-view (3.4.1): Extracting archive
remote: Generating optimized autoload files
remote: 9 packages you are using are looking for funding.
remote: Use the `composer fund` command to find out more!
remote: -----> Preparing runtime environment...
remote: -----> Checking for additional extensions to install...
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 21.6M
remote: -----> Launching...
remote: Released v3
remote: https://pure-fjord-27616-945def8d447f.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/pure-fjord-27616.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
Understanding the 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: heroku-php-apache2 web/
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 starts PHP’s FPM server together with Apache HTTPD, the web server, and instructs it to serve the app from the web/ directory of the codebase.
A Procfile can contain additional process types. For example, you can declare a background worker process that processes items off a queue.
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-05-05T22:03:23.624764+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2 web/`
2026-05-05T22:03:25.024791+00:00 app[web.1]: DOCUMENT_ROOT changed to 'web/'
2026-05-05T22:03:25.149675+00:00 app[web.1]: Available RAM is 512M Bytes
2026-05-05T22:03:25.149690+00:00 app[web.1]: PHP memory_limit is 128M Bytes
2026-05-05T22:03:25.158028+00:00 app[web.1]: Starting php-fpm with 4 workers...
2026-05-05T22:03:25.261266+00:00 app[web.1]: Starting httpd...
2026-05-05T22:03:25.373815+00:00 heroku[web.1]: State changed from starting to up
2026-05-05T22:03:37.967415+00:00 app[web.1]: [2026-05-05T22:03:37.965683+00:00] default.DEBUG: logging output. [] []
2026-05-05T22:03:37.984631+00:00 heroku[router]: at=info method=GET path="/" host=pure-fjord-27616-945def8d447f.herokuapp.com request_id=3fb792da-3c8b-1673-cf28-945e2bc00532 fwd="123.456.789.0" dyno=web.1 connect=0ms service=59ms status=200 bytes=8798 protocol=http1.1 tls=false
2026-05-05T22:03:37.984688+00:00 app[web.1]: 10.1.43.166 - - [05/May/2026:22:03:37 +0000] "GET / HTTP/1.1" 200 8798 "-" "curl/8.7.1
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): heroku-php-apache2 web/ (1)
web.1: up 2026/05/05 17:03:25 -0500 (~ 18s 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.
Install App Dependencies Locally
Heroku recognizes an app as a PHP app by the existence of a composer.json file in the root directory.
The demo app you deployed already has a composer.json:
{
"require": {
"php": "^8.1",
"php-di/php-di": "^7.0",
"slim/slim": "^4.0",
"php-di/slim-bridge": "^3.4.1",
"slim/psr7": "^1.3.0",
...
The composer.json file specifies the dependencies to install with your application. It also determines the version of PHP used to run your application on Heroku, and which PHP extensions to enable.
Every app that has dependencies listed in composer.json also requires the corresponding composer.lock lock file to be present. Any time composer.json is modified, composer update must be run to ensure the lock file is up to date. Running composer install installs the dependencies that were “frozen” to composer.lock during the last update.
When an app deploys, Heroku installs the appropriate PHP and extension packages, as well as regular dependencies, listed in the lock file.
Run composer install in your local directory to install the dependencies, preparing your system for running the app locally:
$ composer install
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Package operations: 47 installs, 0 updates, 0 removals
0 [>---------------------------] 0 [->--------------------------]
- Installing laravel/serializable-closure (v2.0.10): Extracting archive
- Installing psr/log (3.0.2): Extracting archive
- Installing monolog/monolog (3.10.0): Extracting archive
- Installing nikic/fast-route (v1.3.0): Extracting archive
After installing dependencies, you can run your app locally.
Push Local Changes
In this step, you propagate a local change to the application to Heroku.
First, we modify composer.json to include an additional dependency for the alkri11es/cowsayphp library:
$ composer require alrik11es/cowsayphp
./composer.json has been updated
Running composer update alrik11es/cowsayphp
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking alrik11es/cowsayphp (1.2.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing alrik11es/cowsayphp (1.2.0): Extracting archive
Generating autoload files
32 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found.
Using version ^1.2 for alrik11es/cowsayphp
This will modify the “require” section of composer.json for you, and update the composer.lock lock file to reflect the newly installed dependency.
If you introduced the dependency by changing the composer.json file yourself, be sure to update the dependencies by running composer update instead.
The library is now ready for use. Since our example app uses PHP-DI for Dependency Injection, you must first add another entry to the container in app/bootstrap.php, so that PHP-DI knows how to instantiate an animal class instance from the Cowsayphp\Farm namespace when a Cowsayphp\AnimalInterface is used.
In file app/bootstrap.php, on line 23 add:
// Add Cowsay to Container
$container->set(\Cowsayphp\AnimalInterface::class, function() {
return \Cowsayphp\Farm::create(\Cowsayphp\Farm\Cow::class);
});
When defining a new route in app/routes.php, its function’s AnimalInterface type hint will cause PHP-DI to automatically inject a Cow farm animal instance.
In file app/routes.php, on line 14 add:
$app->get('/coolbeans', function(Request $request, Response $response, LoggerInterface $logger, \Cowsayphp\AnimalInterface $animal) {
$logger->debug('letting the Cowsay library write something cool.');
$response->getBody()->write("<pre>".$animal->say("Cool beans")."</pre>");
return $response;
});
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 composer.json composer.lock app/bootstrap.php app/routes.php
Then commit the changes to the repository:
$ git commit -m "Added cowsay library and endpoint"
[main 15aeceb] Added cowsay library and endpoint
4 files changed, 71 insertions(+), 3 deletions(-)
Next, deploy just as before:
$ git push heroku main
Finally, check that everything is working by navigating to the new /coolbeans endpoint of the app:
$ heroku open coolbeans
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 pure-fjord-27616... free
Provisioning has been successful
Created papertrail-curved-63284
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
──────────────────────────────────────────────────────────────────────────────
papertrail (papertrail-curved-63284) choklad free free created
└─ as PAPERTRAIL
The table above shows add-ons and the attachments to the current app (pure-fjord-27616) 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 a one-off command like php -v:
$ heroku run "php -v"
Running php -v on pure-fjord-27616... starting, run.2873
Running php -v on pure-fjord-27616... connecting, run.2873
Running php -v on pure-fjord-27616... up, run.2873
PHP 8.5.5 (cli) (built: Apr 14 2026 11:32:20) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.5, Copyright (c) Zend Technologies
with Zend OPcache v8.5.5, Copyright (c), by Zend Technologies
If you receive an error, Error connecting to process, configure your firewall.
Let’s try another example. Run the bash command to open up a shell on a one-off dyno.
You can use this to view the contents of the dyno’s disk:
$ heroku run bash
Running bash on pure-fjord-27616... starting, run.1801
Running bash on pure-fjord-27616... connecting, run.1801
Running bash on pure-fjord-27616... up, run.1801
~ $ ls -lah
total 192K
drwx------ 11 u15204 dyno 4.0K May 5 22:04 .
drwxr-xr-x 11 root root 4.0K Apr 22 14:59 ..
drwx------ 3 u15204 dyno 4.0K May 5 22:04 .composer
-rw------- 1 u15204 dyno 238 May 5 22:03 .editorconfig
drwx------ 2 u15204 dyno 4.0K May 5 22:03 .github
-rw------- 1 u15204 dyno 23 May 5 22:03 .gitignore
drwx------ 3 u15204 dyno 4.0K May 5 22:03 .heroku
drwx------ 2 u15204 dyno 4.0K May 5 22:03 .profile.d
-rw------- 1 u15204 dyno 29 May 5 22:03 Procfile
-rw------- 1 u15204 dyno 2.5K May 5 22:03 README.md
drwx------ 2 u15204 dyno 4.0K May 5 22:03 app
-rw------- 1 u15204 dyno 199 May 5 22:03 app.json
-rw------- 1 u15204 dyno 421 May 5 22:03 composer.json
-rw------- 1 u15204 dyno 118K May 5 22:03 composer.lock
-rw------- 1 u15204 dyno 591 May 5 22:03 phpunit.xml
drwx------ 2 u15204 dyno 4.0K May 5 22:03 tests
drwx------ 15 u15204 dyno 4.0K May 5 22:03 vendor
drwx------ 2 u15204 dyno 4.0K May 5 22:03 views
drwx------ 4 u15204 dyno 4.0K May 5 22:03 web
~ $
~ $ 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, which you can read using PHP’s getenv function. This way, your app source does not contain hard-coded credentials for databases, external API endpoints and so forth.
In addition to storing credentials such as database connection information, it’s also common practice to place configuration flags or values that control essential behavior of your app into config vars.
For example, the Cowsay library supports other types of animals, and this example will use an environment variable named COWSAY_FARM_CLASS to control which class is used. In web/index.php, locate the section you added earlier, and modify it so that the DI container uses the value of the environment variable to construct the class name dynamically:
// Add Cowsay to Container
$container->set(\Cowsayphp\AnimalInterface::class, function() {
$class = '\\Cowsayphp\\Farm\\'.(getenv("COWSAY_FARM_CLASS")?:'Cow');
return \Cowsayphp\Farm::create($class);
});
If the environment variable is not set, it will default to returning a Cow instance, but if you now set the COWSAY_FARM_CLASS config var on your app to another value, it will be used instead:
$ heroku config:set COWSAY_FARM_CLASS=Dragon
Setting COWSAY_FARM_CLASS and restarting pure-fjord-27616... done, v4
COWSAY_FARM_CLASS: Dragon
To see this change in action, deploy your changed application to Heroku once again, using the same git add web/index.php, git commit, and git push sequence of commands you used earlier.
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 PHP app, view logs, and start a console.
To learn more, see:
Here’s some recommended reading:
- How Heroku Works is a technical overview of the concepts you encounter while writing, configuring, deploying, and running applications.
- Preparing a Codebase for Heroku Deployment contains general deployment recommendations, while Deploying PHP Apps on Heroku offers more PHP-specific guidance.
- The PHP support category contains many resources for managing and deploying PHP apps on Heroku.
- Framework-specific articles contain useful pointers for deploying Laravel and Symfony apps.