All Articles

Running Hasura on the Cheap

What’s Hasura?

Hasura is the company that created the Hasura GraphQL Engine, a “blazing-fast GraphQL server that gives you instant, realtime GraphQL APIs over Postgres.” (For this blog post, I may use “Hasura” and “Hasura GraphQL Engine” interchangeably because this post is about self-hosting their GraphQL engine. But Hasura the company offers more than just their GraphQL engine.)

Hasura Landing Page

You can use the Hasura GUI to create Postgres tables and easily do standard Postgres things like set:

  • columns
  • constraints
  • primary/foreign/unique keys
  • table relationships

Table Relationships in Hasura

All this data is then accessible through a single GraphQL endpoint. You can use the integrated GraphiQL IDE to write queries, mutations, and subscriptions.

GraphiQL in Hasura

Additionally, there are more advanced features like permissioning, custom SQL functions, and event triggers. Beyond their documentation, many of these topics are covered in their extremely handy tutorials.

I’ve been building Trivia.Digital with Hasura as my backend, and it’s made it very simple to configure my database and make my data accessible over a real-time GraphQL API. It’s open-source and has great documentation and I highly recommend it! As a relative noob to both Postgres and GraphQL, its UI is more beginner-friendly than a tool like PostGraphile (though I’ve heard many great things about PostGraphile).

The biggest potential downside of Hasura is that it can be relatively expensive for a hobbyist to use it for a side project. But it doesn’t have to be that way! Because their GraphQL Engine is open-source, I’ve been running it on DigitalOcean for just $5/month.

This post explains how you, too, can run Hasura for $5/month while avoiding some potential pitfalls. Caveat: I am not an expert on any of these topics, so my apologies if I’ve gotten anything wrong. If you have any suggestions, please email me at bhollander823@gmail.com.

Of course, this low bill is only possible because so many companies offer free plans for small projects. A huge thank you to all of these companies for making their tools so accessible.

Expand this if you want to read more about the non-Hasura parts of my stack.
I'm using a 1-5 ⭐ system for how highly I recommend each tool, and I've included some information about their financial situation so you can get a better sense of how likely they are to stick around. Hasura gets five stars from me (and they raised a Series B in September 2020).

⭐⭐⭐⭐⭐

  • Vercel – Vercel is a “deployment and collaboration platform for frontend developers.” I’m hosting my frontend with them. They are the creators of the Next.js React framework and also maintain Geist, a minimal React UI library that I also use in my project. They last raised a Series B round in December 2020.
  • GitHub – If you’re reading this, you probably already know about GitHub. You can host your code on GitHub, open and review pull requests there, etc. Every deploy to my main branch on GitHub automatically triggers a new Vercel deploy. I’m most familiar with GitHub, but other people love GitLab. They were acquired by Microsoft in 2018 for $7.5 billion.

⭐⭐⭐⭐

  • Stream – Stream is a tool that makes it easier to build scalable in-app chat and activity feeds. I’m using it so everyone in a game can talk to each other over text chat. They announced a Series B round in March 2021 and shortly after released a free plan for small businesses.
  • Sentry – Sentry is an application monitoring platform so you can get alerted of any errors in your app. Dead simple to integrate through Vercel. They last raised a Series D round in February 2021.
  • Heap – Heap is a product analytics tool that makes it easy to capture and understand your product usage data. They raised a Series C round in July 2019.
  • Heroku – Heroku is a “cloud application platform” that makes it easy to deploy an app without spending too much time on infrastructure. I’m hosting my Postgres database with them. Purchased by Salesforce in 2010.

⭐⭐⭐

  • Jitsi – An open-source video chat platform. You can even run your video chats on their servers, for free! Less capable than some competitors (like Zoom, who now offer a Video SDK that has a limited free plan) but incredible at the price point. Ownership has changed hands a bit, and was last sold to 8×8 from Atlassian in 2018.
  • Auth0 – An authentication provider that makes it easy enough to let users securely sign in to your app. It’s very nice to not worry too much about security, has its own login page I can redirect users to, and easily integrates with social login providers like Google. I don’t love their login page, so I might replace them with my own auth system. They were sold to Okta for $6.5 billion in March 2021.

Self-Hosting Hasura GraphQL Engine on DigitalOcean

While the free tier of Hasura Cloud is perfect for development, you are limited to 60 requests/minute. That’s probably not going to be enough for a production app. To get unlimited requests, you need to upgrade to the Standard tier which starts at $99/month. Because my site is more of a hobby project, I sought out a cheaper way to run my project in production.

Helpfully, Hasura has documentation on how to easily deploy their GraphQL Engine to DigitalOcean, where a virtual machine with 1 GB memory and 25 GB disk will run you $5/month. Seems like a good deal to me!

Hosting a Postgres Database on Heroku

Hasura additionally has a guide to creating a DigitalOcean managed Postgres database. But those start at a comparatively steep $15/month.

Thankfully, it’s incredibly easy to create a free database hosted on Heroku when you create a new Hasura project. For the low price of free, you can store up to 1 GB across 10k rows. For $9/month, your storage limit dramatically increases to 10 GB across ten million rows.

Create Heroku Database

You might now be thinking, “If I’m going to host my database on Heroku, why don’t I just host Hasura GraphQL Engine on Heroku too?” Well, the approximate Heroku equivalent of a DigitalOcean Droplet is called a ”dyno,” and a dyno that doesn’t auto-sleep starts at $7/month. So a dyno is generally a bit more expensive than the Droplet-equivalent. Still, Heroku is a “Platform as a Service” while DigitalOcean is “Infrastructure as a Service”, so Heroku does offer you additional features for your money if you choose to go that route.

The Biggest Issue With These Hosting Choices

The biggest downside to this approach is that you must manually sync your Heroku database URL with your self-hosted Hasura GraphQL Engine. Before I implemented what I describe below, my site was unusable after Heroku maintenance. It doesn’t matter if you’re using a Dyno, a Droplet, or something else to self-host – your Heroku database credentials will eventually rotate and you’ll want to be prepared.

Why You Need This

There is own major downside to a free Heroku database: maintenance. After maintenance, your database credentials change. So, you must update your database credentials in your self-hosted Hasura GraphQL Engine so your app can continue to connect to your database.

Heroku Maintenance Notice

Hasura Cloud offers ”Heroku URL Sync” to keep your project’s HEROKU_DATABASE_URL in sync. Unfortunately, this feature is only available for Hasura Cloud users. Fortunately, I’m about to tell you how to build this feature for your self-hosted Hasura GraphQL Engine. If all goes well, this should take you less than an hour. If it goes really well, it might only take you 15 minutes. All code below is also available on GitHub.

1. Create a Webhook Server

Webhooks are a way for apps to send automated messages to other apps. In this case, we want Heroku to tell us when our database credentials change so we can update them in our GraphQL Engine.

Adnan Hajdarević’s open-source webhook tool makes it easy to spin-up a server that will receive event notifications via webhook. Your Droplet is probably running Ubuntu 18+, so after SSH’ing into your Droplet, you can install webhook with the following command:

sudo apt-get install webhook

Once webhook is installed, you’ll need to create a configuration file that defines the hooks that webhook will serve.

Here’s what my hooks.json file looks like (except with correct paths, of course):

[
    {
        "id": "redeploy-webhook",
        "execute-command": "/path/to/redeploy.sh",
        "command-working-directory": "/path/to/folder/with/docker-compose.yaml"
    }
]

When the webhook receives the new Heroku release event (we’ll get to that in the next section!) it executes the redeploy.sh script in the directory with our docker-compose.yaml file. Note that we don’t actually care about the event payload – we only care about knowing that something has changed with our Heroku database’s configuration and then running our script.

Our script will fetch the new database URL from Heroku and parse the database password from the URL. It’ll then update our .env file with these new values. Finally, it will restart the Docker containers.

Here’s what the redeploy.sh bash script looks like (make sure it’s excecutable by running chmod +x redeploy.sh):

#!/bin/sh
URL=$(heroku config:get DATABASE_URL -a YOUR-HEROKU-APP-NAME)
PW=$(echo $URL | grep -Po "[a-zA-Z0-9]+?(?=@)")
echo "DATABASE_URI=$URL\nDATABASE_PASSWORD=$PW" > .env
docker-compose restart

Before this script will work, we need to install the Heroku CLI and authenticate ourselves. We can do this on Ubuntu with the following command:

sudo snap install --classic heroku

Then, run:

heroku login -i

and enter your login credentials. You’ll also need to replace YOUR-HEROKU-APP-NAME in redeploy.sh with the name of the app that has the Heroku Postgres add-on installed. You can use the Heroku CLI to get a list of all your Heroku apps by running heroku apps.

Next, start the webhook server with the following command (be sure to replace your-domain.com with an address associated with the Droplet you’ve SSH’d into):

webhook -hooks /path/to/hooks.json -ip "your-domain.com" -port 9003

The hook is now available at http://your-domain.com:9003/hooks/redeploy-webhook

Note that I’ve chosen port 9003 arbitrarily – I cannot vouch for this port beyond that it functionally works for my application and it’s not used by anything else. Also note that the webhook is currently using the insecure http protocol.

To upgrade to https you need to find some certs (more on this in a moment) and then run the following command:

webhook -hooks /path/to/hooks.json -secure -cert /path/to/public.crt -key path/to/private.key -ip "your-domain.com" -port 9003

After running this command, your hook will be available at https://your-domain.com:9003/hooks/redeploy-webhook – much better!

If you’ve deployed the Hasura GraphQL Engine with the app on the DigitalOcean Marketplace, one of its dependencies – Caddy – will automatically obtain and renew TLS certificates for you when you run docker-compose up. These certs are stored in a Docker Volume, so you can peek in there to either provide a path to them or just copy them to a different directory. Do note that the webhook -cert flag expects a public key and the -key flag expects a private key. You can find the full documentation on webhook parameters here.

You will need to be a bit careful not to restart the Docker containers too often as it seems that Hasura’s DigitalOcean Marketplace app isn’t actually re-using TLS credentials across restarts; instead, it’s regenerating new certs each time. And you’re not going to have a great time if you run into Let’s Encrypt’s rate-limit. Addressing this may be a topic of a future blog post. Anyway, the takeaways should be: 1. Consider removing the docker-compose restart line from your redeploy.sh file while you’re testing , and 2. You don’t want to hit this webhook too often.

One last tip: when you run the above webhook command, add an & to the end of it so the command runs in the background – you don’t want the server to stop after you’ve closed your shell session.

2. Configure docker-compose.yaml to Use Environment Variables

This step should be refreshingly straightforward! Instead of directly setting POSTGRES_PASSWORD and HASURA_GRAPHQL_DATABASE_URL in docker-compose.yaml, you should access their values from environment variables. We already named these env vars in redeploy.sh, so you can reference them like so:

POSTGRES_PASSWORD: "${DATABASE_PASSWORD}"

and

HASURA_GRAPHQL_DATABASE_URL: "${DATABASE_URI}"

3. Tell Heroku About Your Webhook URL

We’re in the home stretch!

Navigate to your Heroku app running your Postgres database. The URL will look something like https://dashboard.heroku.com/apps/YOUR-HEROKU-APP-NAME

Then, click More in the top-right and then View webhooks.

Create Heroku Webhook 1 of 2

On the new page, click Create Webhook. Enter in your URL from before – in this case, https://your-domain.com:9003/hooks/redeploy-webhook – and only select the api:release event type. If you’d like to set a secret, refer to the webhook rules documentation.

Create Heroku Webhook 2 of 2

To test your setup, you can create a new config var in your Heroku app. To do this, navigate to the Settings tab in your Heroku app, click Reveal Config Vars and create a new key. It doesn’t matter what you put in as the key name – we just want to create a new release so Heroku calls your webhook.

Set Heroku Config Var

Hopefully, you’re all set! You can double-check by navigating to the directory with your docker-compose.yaml file and seeing that the .env file matches your Heroku database credentials.

Now, any Heroku database maintenance will not be much of a problem for you or your app. Heroku will put your database in read-only mode for up to an hour (but usually much shorter), and you will have just a few seconds of downtime while the credentials are updated.

Conclusion

I hope you found this guide helpful! Again, if you have any suggestions or comments, please email me at bhollander823@gmail.com 👋