Fun with Catalyst and Heroku

For my first experiments with heroku I decided to adapt an existing Catalyst application…

Create and deploy the application

The first step is to create a new heroku app: since I already have a git repo I just added the heroku remote with heroku git:remote --app myapp.

I decided to use Miyagawa’s buildpack which runs any PSGI web application using Starman.

$ heroku buildpacks:set https://github.com/miyagawa/heroku-buildpack-perl

I need an app.psgi in the root of the application to use this buildpack. This is how my .psgi file looks like

#!/usr/bin/env perl
use lib 'lib';
use MyApp;
use Plack::Builder;

builder {
    enable_if { $_[0]->{HTTP_X_FORWARDED_FOR} }
        "Plack::Middleware::ReverseProxy";
    MyApp->psgi_app;
};

I’m using Plack::Middleware::ReverseProxy here since Heroku apps run as a reverse proxy backend.

When pushing code to heroku you can see how the buildpack detects the application and downloads the required software from CPAN.

$ git push heroku
[snip]
remote:
remote: -----> Fetching custom git buildpack... done
remote: -----> Perl/PSGI app detected
remote: -----> Bootstrapping cpanm
remote:        Successfully installed ExtUtils-MakeMaker-7.04 (upgraded from 6.55_02)
[...]

Postgres database

A basic Posgres database can be created using heroku addons:create heroku-postgresql:hobby-dev: to use it I needed to install DBD::Pg and configure the DBI connection.

Since my cpanfile contains an optional feature for adding Postgres support.

feature 'postgres', 'PostgreSQL support' => sub {
   requires 'DBD::Pg';
}

I instructed the buildpack to install the optional module just by using heroku config:set PERL_CPANM_OPT=--with-feature=postgresql.

Note: PERL_CPANM_OPT can also be used to add any additional parameter to cpanm, e.g. use —mirror for a PINTO repo.

All the parameters I needed configure the database connection are in the DATABASE_URL heroku variable, however this cannot be directly used by Catalyst. A nice way to pass them is to create a specific Catalyst configuration file, however in this case I didn’t want to add anything specific for heroku in my repo and I opted for to injecting them to the default configuration using environment variables.

In my MyApp.pm I have added these defaults for connect_info:

__PACKAGE__->config(
    'Model::MyApp' => {
    connect_info => [
        $ENV{MYAPP_DB_DSN} || 'dbi:SQLite:myapp.db',
        $ENV{MYAPP_DB_USERNAME},
        $ENV{MYAPP_DB_PASSWORD},
        { AutoCommit => 1 },
            { quote_names => 1 },
    ],
    });

The variables can be set using the heroku config:set command

heroku config:set 'MYAPP_DB_DSN=dbi:Pg:dbname=xxxx;host=xyz123.amazonaws.com' MYAPP_DB_USER=aabbbccc MYAPP_DB_PASSWORD=wwwyyyzz

Make sure you’re not using memory or file backend for session storage, always use your DB or other storage backend (e.g. Redis).

Leave a comment

About Gabriele Mambrini

user-pic I blog about Perl.