Converting a PHP web application to Heroku from a Shared Host (ex. Host Gator)

For the longest time, I’ve used a shared hosting service (like Host Gator or GoDaddy) for my web applications. Thinking big, hopefully I’ll have a website that I’ll need to scale quickly (and, I have a few other reasons for leaving my current host). I wanted to use Heroku to host my site. This tutorial will explain how to convert a PHP-based web application from a shared host onto Heroku.

Assumption: you have basic knowledge of Heroku and Git.

Prerequisites: you have downloaded:

  • Git
  • Composer
  • Heroku Command Line Interface (“CLI”)

Heroku has a tutorial for getting started, and you should review it. But it doesn’t go into detail about migrating an existing application.

Currently I just upload my files via FTP to the web host. Heroku does things a little differently. Instead, you’ll upload a Git repository to Heroku. Also, you need to use Composer — which is a way of using someone else’s code in your project. Composer is similar to Rails’ “bundle install” or Node.js’ “npm”.

The preparation

From a command line prompt, login to Heroku:

Then create a new project (via command line or the Heroku website). If you’re collaborating with someone else, and the Heroku project is already created, then skip this part of creating a new application.

Ensure you are in your project’s folder, by doing a change directory

Create a Git Repository:

Now you need to connect the files on your local computer to the Heroku servers. Again, if you’re collaborating with someone else, get their application name. Of course, they’ll have to add you as a collaborator within Heroku.

Let’s skip some of the true PHP migration changes for a bit. To get your website from your computer onto Heroku, you need to “deploy” it.

It’s simply running the Git commands to:

  1. Specify the files to upload
  2. Commit the files to the repository
  3. “Push” (“deploy”) the code to a branch (on Heroku)

Here’s how you can do this:

If you haven’t used Composer in your current project, you’ll get a warning about “no composer.json file found”, but it will work anyways. You should be able to view your site now, by visiting http://[your-site-app-name-here].herokuapp.com or from the CLI:

That was the easy part! Now let’s talk about the actual migration from a Shared Host onto Heroku.

The actual conversion

In Heroku’s tutorial, they advise creating a file, called “Procfile” (no extension), and specifying we are running a web application. This tells Heroku you’re going to have a website (as opposed to utilizing one of their other services).

Create a Procfile file in your root directory with this contents:

For a PHP web application, Heroku expects you to use Composer. Create a file called composer.json in the root directory. Technically it could be empty (just “{ }”) as the contents, but let’s specify that we are going to use PHP, and specify which version. If you don’t do this, you’ll get an error later on.

Plan ahead: do this next part now to avoid an error later on:

When you use Composer, you are copying someone else’s code. You specify the files you want in the composer.json file. After you run the “composer update” command, it will create a folder called “vendor” and put the necessary files in that new folder. These files (in the “vendor” folder) should not be stored in the Git repository. Just share the composer.json file with the other developers so they know which files to download/update.

Create a .gitignore file to specify “don’t include the files in the ‘vendor’ folder.”

While we’re at it:

Previously, I used SublimeText with a plugin for FTP. I don’t want to upload that file, so also specify the exclusion of that file in the .gitignore .

My .gitignore contents thus became:

FYI if skipped ahead and already posted the “vendor” folder in your Git staging area, you can remove it with:

Finally, we need to “update” the composer file. This will create a new file called composer.lock.

Run:

and then redeploy to Heroku to ensure everything still works.

Got an error? Me too.

If it’s like my computer (running Windows) using MAMP, you’ll get an error about “The openssl extension is required for SSL/TLS protection but is not available…” .

I used my Shared Host for development (and production), so I never had a web server running on my local computer. For Heroku, it’ll be easier if you do have a local web server.

Start your local web server, and be sure to change the “document root” to where your files are.

Add this to one of your .php files:

Then look for “Loaded Configuration File”. That will tell you the path of your php.ini configuration that’s being used. Remove the “;” (serving as a comment) on the line extension=php_openssl.dll

NOTE: With MAMP on Windows, I ended up having to replace the php.ini file found in the “bin” folder with the one in the “conf” folder. I wrote a blog post about it.

Now if you run “composer update” you shouldn’t get any errors. Go ahead and re-commit to Git and push to Heroku.

You won’t have those errors now!

First you have to pick the kind of database you want. Assuming your application was previously using MySQL (as most PHP applications do on a shared host), we’ll stick with that. Also, this way you won’t have to change the PHP function that calls the database (ex. mysqli_query()). Heroku has an addon called “ClearDB” which is a type of MySQL database that we’ll use.

Enable (or in Heroku terms, “provision”) the ClearDB addon by running this command:

It’ll say that it was defined as a CLEARDB_DATABASE_URL . Ultimately a URL was created in the format of:

mysql://username:password@host/database-name

We’ll go into how to view the URL and why you would need it afterwards, but for now realize that Heroku automatically created an environment variable for us that has this URL. An environment variable is a way to securely store data on the server — think of it like storing information outside of the public directory on your shared web host.

We can use PHP’s getenv() function to get the value of this variable (i.e. the contents of the URL), and then we can use PHP’s function parse_url() to get the values.

Setting the connection string to your database will now look like this:

Here’s an example of how I detect which environment I’m in (development vs production) and set the database connection (and some other variables) accordingly:

The database (part 2 of 3)

A quick note summarizing a problem I ran into:

You’re probably used to a database table where the primary key is an integer that autoincrements by “1”. ClearDB doesn’t do this.

This caused a problem with foreign keys when I pre-populated some data. There are some workarounds to this, but when asking others for advise, they suggested that I just keep this setting as the ClearDB creators intended it; as I may break something by doing the unexpected. So don’t rely on looking at the largest integer in your table for how many rows; you’ll need to use the SQL count() function.

The database (part 3 of 3)

You most likely used phpMyAdmin for administrative purposes (creating tables, viewing records, custom queries, etc.). phpMyAdmin is not available by default. I did find a good alternative though, called Adminer. After you include that (which is a single .php file!) in your application and visit the URL, you’ll be prompted for the username/password/host/database information — i.e. the information from the ClearDB URL.

Remember, this value is stored as an environment variable in Heroku; we can use the heroku config command to view the contents of it. Run one of these (2) commands on the command line:

After you do, it’ll return the value of the CLEARDB_DATABASE_URL in the format previously mentioned.

For example:

mysql://xyz678:abc123@us-cdbr-iron-east-04.cleardb.net/heroku_123?reconnect=true

In this case:

Username is xyz678

Password is abc123

Host is us-cdbr-iron-east-04.cleardb.net

Database Name is heroku_123

… and you can ignore the ?reconnect=true part for now.

And those are the values that you need to enter into Adminer.

As long as we’re talking about environment variables (for the database connection), you should know how to create and access them. Again, this would be used for more sensitive data, such as a database connection string or the connection to an Amazon S3 account. Environment variables are stored on the Heroku servers and the general public can’t see them.

Here’s the official Heroku article explaining about environment variables.

TL;DR version:

The common convention is to name the environment variable with all capital letters.

It’s important that your “development” environment matches your “production” environment to avoid any unforeseen configuration issues. As such, when using a shared host, I typically use my web host to develop websites (I have a keyboard shortcut that uploads right from my text editor to the FTP). Since we’re going away from this, you may find it easier to instead set up a local host on your computer; i.e. have your computer act as both a web server and database server. Popular software for this is XAMP and MAMP.

There are some configuration settings that you won’t have to worry about when you use the shared host as both production and development. But for Heroku, you’ll need to make some adjustments. For example, the ability to upload files, or having sessions. Part of it can be done in the .user.ini file (next section).

Normally you’d create a php.ini file to define custom settings that differ from the defaults; for example, maybe the server defaults to allowing users to upload a 2MB file but you want to increase it to 4MB.

Instead of creating a php.ini file, you need to create a file called .user.ini and put that in your root directory. We’ll also need this file for sessions…

I made a blog post about Heroku PHP Sessions that has some additional detail, but the highlights are below.

One of the few things that Heroku doesn’t support out-of-the-box is PHP session management (i.e. allowing a user to log into your website). With a shared host, it will “just work,” but it needs some tweaking when using Heroku.

At first I didn’t even notice I had a problem: but after I re-deployed my code, all user sessions were reset: i.e. all users had to re-log in to my application!

Here is Heroku’s article explaining PHP Session Handling (which wasn’t enough for me, hence including a blurb about it here).

You need to use Composer to get sessions to work. Specify this in your composer.json file:

From the documentation, it would seem like you need to install ext-memcached on your computer, however I had a lot of trouble doing this. There’s a work-around, found hidden in the Heroku PHP documentation support!

Normally you’d run composer update, but instead run:

… which means that if you don’t have the right software installed on your computer (ex. memcached), then Composer can still compile your code.

Then create/add this to your .user.ini file:

Once we have all code ready, we now need to tell Heroku that we want to use an addon for session management. On the command line, run:

This is another setting that might change when you’re developing on your local machine vs the old shared host vs Heroku.

Here are some tips:

  • Be sure to look at the “file upload error” (ex. $_FILES[‘filePicture’][‘error’]). For me, I could have saved a lot of time if I just realized that “error 1” means “file size is too big per php.ini file”.
  • The function “move_uploaded_file()” doesn’t seem to be supported by Heroku . Don’t use it! (Luckily I was able to remove it from my code without causing additional issues.)
  • I was using a Composer package to upload files to Amazon. It worked with MAMP, but I couldn’t get it to work with XAMP (specifically, I had trouble with setting the Environment Variables on a Windows machine). I ended up not using the Composer Package for S3 uploads (and I found a PHP S3 class instead). Don’t feel you have to use Composer for everything.
  • As with the sessions that you needed to update the php.ini file: create a .user.ini file to specify values. For example, to change the maximum file size allowed for an upload, use this (changing your value as needed)
  • I needed to upload a picture, and then create a thumbnail from it. So I needed to retain the “original” picture that was uploaded, and manipulate the “temporary file” stored. Heroku doesn’t have file extensions in the names of their temporary files, so my existing code did not work (specifically, because I was looking for a “.” in the file name to parse the file extension).
  • To manipulate images (ex. create a thumbnail), I needed to add in the “gd” library in my composer.json file. This was on my shared host by default (so I thought it would be everywhere by default), but it was not included in Heroku’s default configuration. Here’s the updated composer.json file:

You may want to use basic authentication to password-protect your site. The location of the file that has the passwords must be in the “/app/” directory (which is actually where your code is). Now, I comment/uncomment the appropriate lines when developing vs deploying to Heroku:

By the way, to generate the password for a .htaccess site, I’ve used http://www.htaccesstools.com/htpasswd-generator/

.htaccess files can be used for URL re-writing (ex. SEO purposes). I ran into an odd problem where, when viewing my site on SSL, it would mysteriously redirect to the non-SSL version. The only solution I found was that when I explicitly stated I was redirecting the user to /index.php (instead of whatever subdirectory I wanted), did it work. I ended up contacting Heroku tech support, who was very helpful.

The solution is to use .htaccess and force the user to remain on SSL. But there’s a catch here: as there is a line of code I needed to add for it to work on Heroku’s servers:

Summary for migrating a PHP web application to Heroku

  • Use Git if you’re not already. Add “/vendor” to your .gitignore
  • Create a Procfile
  • Create a composer.json file, and run: composer update… if/when you get stuck with the memcache for PHP Sessions, instead run:
  • Provision a ClearDB database, which is a type of MySQL database. Watch out, as the auto-IDs won’t increment by “1”
  • Use environment variables to connect to the database and store other connection strings (ex. Amazon S3)
  • For PHP Sessions, provision the memcachier addon, add to your .user.ini file, and update your composer file to include ext-memcached
  • For image upload/manipulations, add the ext-gd library to your composer file. Your existing code may not work without some tweaks
  • .htaccess passwords may be in a different location. To avoid the error of a user mysteriously switching between SSL and non-SSL, force them to remain on SSL using RewriteCond %{HTTP:X-Forwarded-Proto} !https

Like this style of writing? 👍👍 Are you creating a PHP web application and already know what a “variable” is and the concept of an “if” statement? Check out my book: Web Development for Intermediate Programmers — with PHP

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store