Deploying Symfony 3 Apps on Heroku
Last updated May 30, 2024
Table of Contents
This guide will walk you through the steps of deploying a Symfony version 3 application on Heroku. For an introduction to using PHP on Heroku, please refer to Getting Started with PHP on Heroku.
This article is for Symfony 3. If you’re instead looking to deploy a Symfony 4 (or later) application, please refer to the corresponding Deploying Symfony apps on Heroku article.
Prerequisites
- Knowledge of the PHP language;
- A Heroku user account, signup is free and instant;
- Familiarity with the getting Started with PHP on Heroku guide, with PHP, Composer and the Heroku CLI installed on your computer;
- An existing Symfony 3 application you’d like to deploy (or an empty skeleton app, see below).
Creating a Symfony application
If you’d like to simply follow the along this guide, you may create an empty Symfony 3 application using the following steps.
The application in this tutorial is based on the Symfony Quick Tour guide. It’s worth a read before (or while) following the instructions in this article.
Installing a Symfony Standard Edition project
Use the composer create-project
command to bootstrap a new project based on the Symfony Standard Edition, which is a fully functional Symfony application which includes some sample code. The command below sets it up in a directory named symfony3-heroku
using the latest version of Symfony.
After downloading an extensive number of dependencies, the installer will prompt you to enter a few details regarding a database connection and mailer transports. For now, you can simply hit enter at each of these prompts to accept the default values; you can always change them later.
After it’s done, Composer has set up a fully functional Symfony Standard Edition project in the directory you specified, so you can cd
to it.
$ composer create-project symfony/framework-standard-edition:^3.0 symfony3-heroku/
Installing symfony/framework-standard-edition (v3.4.28)
- Installing symfony/framework-standard-edition (v3.4.28): Downloading: 100%
Created project in symfony3-heroku/
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
…
$ cd symfony_heroku
Initializing a Git repository
It’s now time to initialize a Git repository and commit the current state:
$ git init -b main
Initialized empty Git repository in ~/symfony3-heroku/.git/
$ git add .
$ git commit -m "initial import"
[main (root-commit) 241cbc8] initial import
42 files changed, 4397 insertions(+)
…
Initial Setup
You’re now going to create an application on Heroku, configure the Symfony environment to use, and then you can simply git push
to deploy for the first time!
Creating a new application on Heroku
To create a new Heroku application that you can push to, use the CLI’s create
command:
$ heroku create
Creating app... done
https://floating-badlands-41656.herokuapp.com/ | https://git.heroku.com/floating-badlands-41656.git
This command will create a new remote named “heroku” on your local Git repository.
Creating a Procfile
To deploy your application to Heroku, you must often first create a Procfile
, which tells Heroku what command to use to launch the web server with the correct settings. By default, Heroku will launch an Apache web server together with PHP to serve applications.
However, a special circumstance applies to your Symfony application: the document root is in the web/
directory, and not in the root directory, of the application.
Some older versions of Symfony sometimes created this file automatically, but you should explicitly create this Procfile
as part of your application, something that will become necessary anyway once you begin using additional process types.
Simply create a file with the correct command for the web
process type and commit it:
$ echo 'web: heroku-php-apache2 web/' > Procfile
$ git add Procfile
$ git commit -m "Heroku Procfile"
Configuring Symfony to run in the prod
environment
If you don’t explicitly configure the environment (dev
, prod
, etc.) to use, Symfony will, by default, use the dev
environment in console commands and at runtime.
For Symfony to know it needs to use the prod
environment at all times, it reads from the SYMFONY_ENV
environment variable. You can set environment variables using the heroku config
feature. Running following command will set a configuration variable that lets Symfony to know to run in production mode:
$ heroku config:set SYMFONY_ENV=prod
Setting config vars and restarting floating-badlands-41656... done
SYMFONY_ENV: prod
Deploying to Heroku
Next up, it’s finally time to deploy to Heroku for the first time:
$ git push heroku main
…
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Installing system packages...
…
remote: -----> Installing dependencies...
remote: Composer version 1.8.6
remote: Loading composer repositories with package information
remote: Installing dependencies from lock file
…
remote: -----> Preparing runtime environment...
remote: -----> Checking for additional extensions to install...
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing... done, 87.3MB
remote: -----> Launching...
remote: Released v4
remote: https://floating-badlands-41656.herokuapp.com/ deployed to Herok
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/floating-badlands-41656.git
* [new branch] main -> main
And that’s it! If you now open your browser, either by manually pointing it to the URL heroku create
gave you, or by using the Heroku CLI to launch a new browser window, the application will respond:
$ heroku open
Opening floating-badlands-41656... done
It is likely that you have to set additional environment variables, enable logging, and so forth, for your application to be functional, so follow the next steps to finalize your deployment.
Logging
By default, the Symfony app will log into your application’s app/log/
directory, which isn’t ideal as Heroku uses an ephemeral filesystem and treats logs as streams of events across all running dynos. On Heroku, the best way to handle logging is using Logplex, and the best way to send log data to Logplex is by writing to STDERR
or STDOUT
. Luckily, Symfony uses the excellent Monolog library for logging, and so a new log destination is just a config file change away.
Changing the log destination for production
All that’s required to have Symfony log to STDERR
is changing app/config/config_prod.yml
. Locate the monolog
/handlers
/nested
section in this file and change the value of path
from "%kernel.logs_dir%/%kernel.environment%.log"
to "php://stderr"
, so it looks roughly like this:
monolog:
handlers:
nested:
type: stream
path: "php://stderr"
level: debug
You can now git add
, git commit
, and git push heroku main
that change as usual.
Viewing application logs
Next, run heroku logs --tail
to keep the stream of logs from Heroku open in your terminal. Switch back to your browser and navigate to your Heroku application. As you refresh the page, you’ll see the web server’s access logs on your terminal, as well as anything your application may be logging.
Press Ctrl+C
on your keyboard again to leave the heroku logs --tail
command.
Environment variables
Your application will likely want to connect to a database, communicate with an email gateway, or just dynamically react to configuration. For this purpose, Heroku will expose any config var, whether it was set by yourself or by an add-on, as an environment variable that you may read in PHP code from the $_ENV
superglobal or using getenv()
.
Using config vars to define environment specific info such as database credentials, log levels, or e-mail gateway information is one of the fundamental principles of the Twelve-Factor App.
Since version 3.2, Symfony supports dynamic resolution of environment variables at runtime, so you can simply reference them using the new syntax without having to worry about caching:
doctrine:
dbal:
url: "%env(DATABASE_URL)%"
Trusting the Heroku Router
Heroku’s HTTP Routing routes each request through a layer of reverse proxies which are, among other things, responsible for load balancing and terminating SSL connections. This means that requests received by a dyno will have the last router’s IP address in the REMOTE_ADDR
environment variable, and the internal request will always be made using the HTTP protocol, even if the original request was made over HTTPS.
Like most common load balancers or reverse proxies, Heroku provides the original request information in X-Forwarded-…
headers (as documented here). Symfony can easily be configured to trust such headers.
Since Heroku sends all requests to an application through a load balancer first, and that load balancer always sets the headers (making it impossible for a client to forge their values), you can configure Symfony to treat the current remote addresses (which is the Heroku router) as a trusted proxy in app.php
.
It is important to also prevent Symfony from trusting the Forwarded
and X-Forwarded-Host
headers, because Heroku’s router does not set those, but Symfony trusts them out of the box once a trusted proxy is set.
This can be achieved by setting the proxy trust method to HEADER_X_FORWARDED_AWS_ELB
.
The final code, in your web/app.php
, then looks as follows:
…
$request = Request::createFromGlobals();
…
Request::setTrustedProxies(
// trust *all* requests
['127.0.0.1', $request->server->get('REMOTE_ADDR')],
// only trust X-Forwarded-Port/-Proto, not -Host
Request::HEADER_X_FORWARDED_AWS_ELB
);
…
After a git add
, git commit
and git push heroku main
, your code will be able to access the correct values for remote address, server port, hostname and protocol.
With this approach, if you deploy your application to other environments, you must similarly ensure that all traffic arrives through a trusted source, or conditionally only set the trusted proxy when you detect Heroku as the environment your application is running in.
Using the Nginx Web Server
In the initial deployment step further above, the Procfile
you created uses the heroku-php-apache2
command to launch the application. The .htaccess
file bundled with Symfony Standard Edition applications ensures that URLs are correctly rewritten to the app.php
“front controller” script. You may instead use Nginx to run your application.
Creating an Nginx configuration include
Nginx does not support a mechanism similar to Apache’s .htaccess
configs, so you have to create a dedicated configuration include and instruct Nginx to use it.
For a Symfony application, the rewrites needed are very simple; you can place the following directives into a file named nginx_app.conf
in your application’s root directory:
location / {
# try to serve file directly, fallback to rewrite
try_files $uri @rewriteapp;
}
location @rewriteapp {
# rewrite all to app.php
rewrite ^(.*)$ /app.php/$1 last;
}
location ~ ^/(app|app_dev|config)\.php(/|$) {
try_files @heroku-fcgi @heroku-fcgi;
# ensure that /app.php isn't accessible directly, but only through a rewrite
internal;
}
Creating a Procfile
for Nginx
This time around, you will use the heroku-php-nginx
script, and pass it your custom configuration snippet using the -C
option:
$ echo 'web: heroku-php-nginx -C nginx_app.conf web/' > Procfile
$ git add Procfile nginx_app.conf
$ git commit -m "Nginx Procfile and config"
After the next git push heroku main
, your application will run using the Nginx web server.
Further reading
- Check out the PHP on Heroku Reference to learn about available versions, extensions, features and behaviors.
- Review the instructions on customizing web server and runtime settings for PHP to learn more about configuring Apache, Nginx and PHP.
- Browse the PHP category on Dev Center for more resources.