When spinning up a new project recently, I decided to try out Heroku CI, and I’m pleased to report that there’s a lot to like. If you’re already running your app on Heroku, adding Heroku CI is a breeze. And because you only pay for the dyno time you use, you can choose high-performance dynos for fast builds without breaking the bank.

Pay by the minute, but you probably want a limit

After enjoying pleasantly-quick builds on Heroku CI for a few weeks, I woke up one morning to a build that ran for 2 hours! Since my builds were typically completing in less than 2 minutes, you can probably imagine my surprise. 😳

Screenshot of build that timed out after 2 hours

And remember that bit about only paying for what you use? Well, apparently this build had used 125 minutes of dyno time, which means that this one build cost about 6,000% more than my normal builds. 🤭

At $0.69/hour (USD) for a performance-l dyno1, it’s not a huge deal if this happens once or twice, but I certainly didn’t want it to become a common occurrence.

Set a timeout to prevent runaway builds

Heroku’s system-wide timeout will cancel builds after 2 hours, but Heroku doesn’t currently provide a way for you to configure a shorter timeout. Fortunately though, you can roll your own custom timeout with just a few keystrokes. 😅

In your app.json manifest, you can tweak your setup and test commands to use the timeout shell command to enforce a custom time limit. For example, I expect my setup to complete within 2 minutes, and I expect my tests to complete within 1 minute, so I changed my app.json as follows:

--- a/app.json
+++ b/app.json
@@ -4,8 +4,8 @@
     "test": {
       "addons": ["heroku-postgresql:in-dyno", "heroku-redis:in-dyno"],
       "scripts": {
-        "test-setup": "./script/ci-test-setup",
-        "test": "./script/ci-test"
+        "test-setup": "timeout 2m ./script/ci-test-setup",
+        "test": "timeout 1m ./script/ci-test"
       },
       "formation": {
         "test": {

With those changes in place, timeout will terminate ./script/ci-test-setup if it doesn’t complete within 2 minutes, and it will terminate ./script/ci-test if it doesn’t complete within 1 minute.2

Knowing that my builds are limited to a specific duration that’s tuned for my application, I can once again enjoy Heroku’s high-performance dynos for super-fast CI builds, without the risk of waking up to a surprising dyno bill.

  1. 500 USD/month / (30 days/month * 24 hours/day) = 0.69 USD/hour 

  2. If your app.json manifest doesn’t yet include test-setup and test commands, it means you’re using the default test-setup and test commands for your application’s buildpack. To enforce a custom timeout, check the Heroku docs to find the default test-setup and test commands used for your application’s buildpack. Then, add those commands to your app.json manifest and prepend them with your desired timeout.