Using a monolith repository with Heroku

Nowadays, most of the projects (and/or products) are going towards splitting responsibilities in multiple applications. You will have a pure-JavaScript (ish, could be compiled using things like Elm) front-end and a backend API in any programming language.

How can you deploy your front-end and backend applications from a monolith repository to Heroku?

If you use different Git repositories for each of these applications you won’t have any problem. But I have to admit, I’m usually an advocate of monolith repositories. If you want to know why, this article is a good summary.

Image for post
Image for post
Art of monolith repository

Using different Heroku applications

In order to achieve this goal, you could use different Heroku applications and deploy these applications by running the heroku commands in the right directory from your continuous integration pipeline. This would work but there are multiple drawbacks:

Instead, you can deploy your different front-end/backend applications within the same Heroku application buy using multiple build packs.

Multiple applications to a single Heroku application

First of all, did you know Heroku supports multiple buildpacks? On top of that, Mark Miyashita built a buildpack that supports different buildpacks in different sub-folder of your repository.

Let’s take the example of a React front-end and a Symfony backend. The backend will require a database. We will get started by writing an app.json file to automate the creation of the application and its add-ons:

# app.json
{
"buildpacks": [
{
"url": "https://github.com/negativetwelve/heroku-buildpack-subdir"
}
],
"addons": [
{
"plan": "heroku-postgresql",
"options": {
"version": "9.5"
}
}
],
"env": {
"NPM_CONFIG_PRODUCTION": {
"value": "false"
}
},
"formation": {
"web": {
"quantity": 1,
"size": "hobby"
}
}
}

Configure the buildpacks for each application

Now, to use the heroku-buildpack-subdir buildpack, you need to define which buildpack will be used into which directory in a .buildpacks file at the root of the repository. We imagine that you will have the following folder structure in your Git repository:

frontend/
src/
...
package.json
api/
src/
...
composer.json
.buildpacks

This .buildpacks file contains the following “folder-buildpack mapping”:

frontend=https://github.com/heroku/heroku-buildpack-nodejs.git#v118
api=https://github.com/heroku/heroku-buildpack-php.git#v131

Both applications needs to be accessible from the web

An Heroku application can have multiple dynos. But there is one restriction: only the web dyno is accessible from the Internet. In order to make this work we will configure nginx (shipped within PHP’s buildpack) that way:

The nginx.conf configuration file matching this requirement looks like that:

location ^~ /api/ {
alias /app/api/public/;

if (!-e $request_filename) {
rewrite ^ /api/index.php last;
}

location ~ \.php$ {
include fastcgi_params;

fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param PATH_INFO $fastcgi_path_info if_not_empty;

fastcgi_pass heroku-fcgi;
internal;
}
}

# Set the cache headers
location = /index.html {
expires 1s;
}

location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
expires 30d;
}

# Front-end files
location ~ ^/ {
try_files $uri @filedonotexists;
}

# If the file do not exists, we use the SPA index page
location @filedonotexists {
try_files $uri /index.html;
}

When customising anything, in dockerfiles or Heroku’s buildpacks, I quite like clarifying how the plumbing works by creating a decidated startup script. I’ll write a heroku/start bash script that will run API’s nginx with our custom configuration:

#!/bin/bash
set -xe

# Start Heroku's PHP + nginx buildpack
pushd api
exec ./vendor/bin/heroku-php-nginx -C ../heroku/configuration/nginx.conf ../frontend/dist/

Last, but not least, configure your web dyno to use this script in a Procfile at the root of the code repository:

web: ./heroku/start

Now, all you need to do is to push this configuration in your favourite code repository and go the the following URL to deploy your application:

Happy monolith!

Written by

Software Engineering. Containers. APIs & IPAs.

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