SuperGeekery: A blog probably of interest only to nerds by John F Morton.

A blog prob­a­bly of inter­est only to nerds by John F Mor­ton.

Configuring Craft CMS with Redis for use on Heroku

This post is about con­nect­ing a Heroku app built in Craft CMS to Heroku Redis with an SSL con­nec­tion.

I’ve recent­ly tak­en an app from the free tier to a paid tier on Heroku and was pre­sent­ed with a prob­lem with con­nect­ing my Craft CMS app to Redis. I hope this post is help­ful to some­one else who’s found them­selves in this sit­u­a­tion.

Upgrad­ing Heroku Redis from the free tier to a paid tier requires that you con­nect to Redis through a secure con­nec­tion. This dif­fer­ence is why your free” app might work but your paid” app will appear bro­ken. Look­ing through the Github issues on this top­ic, you may find this post, Does­n’t work with Heroku Redis (TLS required, Redis 6+). The solu­tion men­tioned in one of the com­ments turned out to be the solu­tion. In short, include this in your con­fig.

'useSSL' => true,
   'contextOptions' => [
   'ssl' => [
      'verify_peer' => false,
      'verify_peer_name' => false,
   ],
],

This sug­ges­tion does work. You could prob­a­bly con­fig­ure the app to have the SSL option val­i­date the peer and peer name, but that’s beyond the scope of what I’m writ­ing about today though.

Why is there any more to this post?

If that’s the solu­tion, you might won­der why this post exists. Fair ques­tion!

Even though I had it con­fig­ured to work and it was work­ing, I was under the impres­sion it was incor­rect. I would log into the serv­er via the com­mand line with heroku ps:exec and then try to run the ./craft com­mand. It report­ed that the redis data­base is not reach­able as con­fig­ured.”


Screenshot of Heroku error

I rewrote my app.php many times. Local­ly, I was able to con­nect to the Heroku Redis but when I was on the Heroku serv­er, I would always receive this error. To my sur­prise, just look­ing at the app in my brows­er, every­thing seemed fine. It’s just that this error mes­sage on the com­mand line seemed like some­thing I need­ed to fix.

The light­bulb moment for me was when I removed Redis from my app entire­ly. My expec­ta­tion was that since Craft would then rely on its own data­base for stor­ing cache requests, the com­mand line ./craft com­mand would work. The sur­prise is that it did­n’t.

Remov­ing Redis pro­duced a dif­fer­ent error mes­sage:

The database is not reachable as configured.

How can that be? I can see my Craft web­site is access­ing the data­base because I can see the entries. Why would the ./craft com­mand report that it can’t access the data­base?

Looking through the Heroku docs

I often think of using the com­mand line on a Heroku serv­er as SSH-ing into the serv­er.” This ter­mi­nol­o­gy is not entire­ly accu­rate though. You can’t sim­ply use SSH to log into a Heroku serv­er. You use the heroku-cli and the Heroku Exec com­mand, as described in the doc­u­men­ta­tion.

Heroku Exec is a fea­ture for cre­at­ing secure TCP and SSH tun­nels into a dyno. It allows for SSH ses­sions, port for­ward­ing, remote debug­ging, and inspec­tion with pop­u­lar Java diag­nos­tic tools.

Near the end of the doc­u­men­ta­tion, there is a sec­tion on lim­i­ta­tions of the ps:exec com­mand.

Specif­i­cal­ly, there are lim­i­ta­tions when it comes to envi­ron­men­tal vari­ables.

The SSH ses­sion cre­at­ed by Heroku Exec will not have the con­fig vars set as envi­ron­ment vari­ables (i.e., env in a ses­sion will not list con­fig vars set by heroku config:set).

This is the key.

Let’s review the sit­u­a­tion again. The Craft com­mand-line tool report­ed it could­n’t access the Redis data­base even when it appeared to be work­ing. Then, after remov­ing Redis from Craft entire­ly, the com­mand-line tool then report­ed that it could­n’t access the data­base itself, even though it obvi­ous­ly could when look­ing at the site in a brows­er.

The Redis con­nec­tion prob­lem and the data­base con­nec­tion prob­lems are not due to a mis­con­fig­ured Craft app. The prob­lem is that the ./craft com­mand, when run from Heroku using the ps:exec com­mand does not have access to any of the envi­ron­men­tal vari­ables we typ­i­cal­ly expect to have access to.

With­out any cre­den­tials, the ./craft com­mand can’t do any­thing to any data­base. I don’t know that it is pos­si­ble to run any ./craft com­mand from the com­mand line when using Heroku. That’s a bum­mer, but at least I’ve got Redis work­ing on my app. ¯\_(ツ)_/¯

Heroku Dashboard

I’ll wrap up with a screen­shot of how my app is con­fig­ured in the Heroku Dash­board. You can see I have a web dyno, con­fig­ured to 2x, but use what works for you. I have a work­er. I have a ClearDB MySQL data­base and I have Heroku Redis.


2022 03 02 09 58 38

Procfile

My Proc­file is where the web dyno and work­er dynos were cre­at­ed.

web: vendor/bin/heroku-php-apache2 web
worker: ./craft queue/listen --verbose

You’ll need to also upgrade the work­er after you cre­ate it to have at least 1 dyno. That is what you see in the screen­shot above.


app.php & app.web.php

It may be help­ful to you to see my mod­i­fied app.php and app.web.php files. These are mod­i­fied ver­sions of the app.php and app.web.php file from Andrew Welch’s Craft con­fig­u­ra­tion.

app.php

<?php
/**
 * Yii Application Config
 *
 * Edit this file at your own risk!
 *
 * The array returned by this file will get merged with
 * vendor/craftcms/cms/src/config/app.php and app.[web|console].php, when
 * Craft's bootstrap script defines the configuration for the entire
 * application.
 *
 * You can define custom modules and system components, and even override the
 * built-in system components.
 *
 * If you want to modify the application config for *only* web requests or
 * *only* console requests, create an app.web.php or app.console.php file in
 * your config/ folder, alongside this one.
 */
use craft\helpers\App;

$redisUrl = App::env('REDIS_URL');

if (!$redisUrl) {
    exit('app.php: The redis database is not reachable as configured:' . App::env('REDIS_URL'));
}

$redisSettings =  [
    'id' => App::env('APP_ID') ?: 'CraftCMS',
    'modules' => [
        'site-module' => [
            'class' => \modules\sitemodule\SiteModule::class,
        ],
    ],
    'bootstrap' => ['site-module'],
    'components' => [
        'cache' => [
            'class' => yii\redis\Cache::class,
            'defaultDuration' => 86400,
            'keyPrefix' => App::env('APP_ID') ?: 'CraftCMS',
            'redis' => [
                'hostname' => parse_url(App::env('REDIS_URL'), PHP_URL_HOST),
                'port' => parse_url(App::env('REDIS_URL'), PHP_URL_PORT),
                'database' => App::env('REDIS_CRAFT_DB'),
                'password' => App::env('REDIS_USE_SSL') ? parse_url(App::env('REDIS_URL'), PHP_URL_PASS) : null,
                'useSSL' => App::env('REDIS_USE_SSL'),
                'contextOptions' => [
                    'ssl' => [
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                    ],
                ],
            ],
        ],
        'deprecator' => [
            'throwExceptions' => App::env('DEV_MODE'),
        ],
        'queue' => [
            'class' => craft\queue\Queue::class,
            'ttr' => 10 * 60,
        ],
        'redis' => [
            'class' => yii\redis\Connection::class,
            'hostname' => parse_url(App::env('REDIS_URL'), PHP_URL_HOST),
            'port' => parse_url(App::env('REDIS_URL'), PHP_URL_PORT),
            'database' => App::env('REDIS_DEFAULT_DB'),
            'user' => App::env('REDIS_USE_SSL') ? parse_url(App::env('REDIS_URL'), PHP_URL_USER) : null,
            'password' => App::env('REDIS_USE_SSL') ? parse_url(App::env('REDIS_URL'), PHP_URL_PASS) : null,
            'useSSL' => App::env('REDIS_USE_SSL'),
            'contextOptions' => [
                'ssl' => [
                    'verify_peer' => false,
                    'verify_peer_name' => false
                ],
            ],
        ],
    ],
];

return $redisSettings;

app.web.php

<?php
/**
 * Yii Web Application Config
 *
 * Edit this file at your own risk!
 *
 * The array returned by this file will get merged with
 * vendor/craftcms/cms/src/config/app.php and app.[web|console].php, when
 * Craft's bootstrap script defines the configuration for the entire
 * application.
 *
 * You can define custom modules and system components, and even override the
 * built-in system components.
 *
 * This application config is applied only for *only* web requests
 */

use craft\helpers\App;

return [
    'components' => [
        'session' => static function() {
            // Get the default component config
            $config = App::sessionConfig();
            // Override the class to use Redis' session class and our config settings
            $config['class'] = yii\redis\Session::class;
            $config['keyPrefix'] = App::env('APP_ID') ?: 'CraftCMS';
            $config['redis'] = [
                'hostname' => parse_url(App::env('REDIS_URL'), PHP_URL_HOST),
                'port' => parse_url(App::env('REDIS_URL'), PHP_URL_PORT),
                'database' => App::env('REDIS_DEFAULT_DB'),
                'password' => App::env('REDIS_USE_SSL') ? parse_url(App::env('REDIS_URL'), PHP_URL_PASS) : null,
                'useSSL' => App::env('REDIS_USE_SSL'),
                'contextOptions' => [
                    'ssl' => [
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                    ],
                ],
            ];
            // Instantiate and return it
            return Craft::createObject($config);
        },
    ],
];

In my local envi­ron­ment, I access Redis in a Dock­er con­tain­er on my local machine. It does­n’t require any cre­den­tials since it’s local. I also set the useSSL val­ue to false.

REDIS_URL=redis://:@redis:6379
REDIS_USE_SSL=false

The Heroku equiv­a­lent is some­thing like the exam­ple you see below. It will change over time, so be sure to use the env vari­able they pro­vide so your con­fig­u­ra­tion remains cur­rent as they update the account cre­den­tials. I also set the useSSL val­ue to true.

Dur­ing debug­ging, I used this remote Redis vari­able local­ly to do my debug­ging but changed it back to the Redis in my Dock­er con­tain­er after I got it con­fig­ured cor­rect­ly for my Heroku app.

REDIS_URL=rediss://:somelongstringfromamazon123456@ec2-12-345-678-901.compute-1.amazonaws.com:12345
REDIS_USE_SSL=true

Some useful commands and an example Github repo

I’ll wrap this up with some help­ful com­mands. There’s a good chance you know these, but they were help­ful in the dis­cov­ery process I out­lined above.

To see the real-time logs from your Heroku app, use this com­mand.

heroku logs --tail -a my-app-name

See the Redis logs only

heroku logs -s redis -t -a my-app-name

Final­ly, check out my Craft CMS Heroku Starter repo on Github.

Good luck with con­fig­ur­ing your Heroku app. I hope this helped out.