DEV Community

Cover image for How to trigger a manual redeploy on Heroku from code - A killer feature to update your Gatsby setup that isn't documented
Matteus Deloge
Matteus Deloge

Posted on • Edited on

How to trigger a manual redeploy on Heroku from code - A killer feature to update your Gatsby setup that isn't documented

TL;DR: You can easily retrigger an Heroku build for your Gatsby service to ingest Strapi CMS data, you only need to execute a POST request on an undocumented weird subdomain from Heroku called **kolkrabbi.heroku.com. See further down for API call details and Strapi CMS example implementation.

So I got a request from Conversation Starter, a customer from one of my freelance assignments (I'm a tech lead at Craftworkz by day and an IT freelancer by night), that they wanted to setup a headless CMS system to manage and host their blog posts for their existing website, built with the amazing Gatsby framework.

The choice was immediately made to use Strapi as the CMS system, mainly because of the great reviews it gets in the community and the many tutorials that are available.

Development went smoothly but then, deployment time had arrived. Both Strapi and the Gatsby website run happily on Heroku dynos, but due to the nature of Gatsby (static hosting of content) we need to rebuild the website on each change to the published blog post articles in the CMS.

One would think that a simple rebuild of an existing deployment on Heroku is a simple and well documented thing, right?

Well, it's not.

After a long search, even considering pushing empty git commits from the Strapi CMS to the Gatsby repository to trigger a new build, I found a breadcrumb that would lead me to the solution for my problem: a manual redeploy button on Heroku itself.

Alt Text

Well isn't this nice! That's exactly the feature I needed. However, this is documented nowhere in the official Heroku docs (send me the link if you would find it).

Using my trusted Chrome dev tools I luckily was able to document the API call myself, and again lucky: it was very easy to replicate without doing too much weird stuff. The only weird thing here is that you need to call an unknown Heroku subdomain, called kolkrabbi.heroku.com

The API call looks like this:

POST https://kolkrabbi.heroku.com/apps/<HEROKU_APP_ID>/github/push

Headers: {
    "Authorization": "Bearer <USER_AUTH_TOKEN>"
}

Body: {
    "branch": "master"
}
Enter fullscreen mode Exit fullscreen mode

Where:

  • HEROKU_APP_ID = your app's UUID which you can fetch using the heroku apps:info --json command
  • USER_AUTH_TOKEN = your own user's access token. Go to https://dashboard.heroku.com/account and navigate to the API Key section
  • Within the body of your call you can then choose which branch to build.

Please note that if you have multiple instances of your application (such as development and production), you will have a different App ID.

And as a cherry on the cake, I'll post my Strapi config here as well. The idea is that every time an Article model instance is being created, updated or deleted, the Heroku build function is being triggered so that Gatsby can ingest the new changes.

./api/articles/models/article.js

'use strict';

const axios = require('axios');

/**
 * Lifecycle callbacks for the `Article` model.
 */

var deployUrl = `https://kolkrabbi.heroku.com/apps/${process.env.HEROKU_APP_ID}/github/push`

var axiosConfig = {
    headers: {
        "Authorization": `Bearer ${process.env.CS_HEROKU_BUILD_TOKEN}`,
        "Content-Type": "application/json"
    }
}

async function callDeployEndpoint(deployUrl, branch) {
    await axios
        .post(deployUrl, {
            "branch": branch
        }, axiosConfig)
        .then((res) => {
            strapi.log.debug(`Executed build trigger for branch ${branch} `);
        })
        .catch((error) => {
            strapi.log.error(`Caught error for branch ${branch}: `, error);
        });
}

async function triggerDeploy() {
    strapi.log.debug('Triggered redeploy');
    strapi.log.debug('axiosConfig: ', axiosConfig);
    await callDeployEndpoint(deployUrl, "master");

}

module.exports = {
    lifecycles: {
        async afterCreate(data) {
            await triggerDeploy();
        },
        async afterUpdate(data) {
            await triggerDeploy();
        },
        async afterDestroy(data) {
            await triggerDeploy();
        },
    }
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)