Save $100/mo with custom domains on Vercel
I love the idea of review apps or pull request. Where your code in a pull request is server live.
I had recently changed the backend of our services to only issue cookies on our personal domain, let's call it myapp.com
. Since we were using Heroku and later Vercel that means .herokuapp.com
and .vercel.app
were no longer usable suffixes for our review apps.
Vercel offers a custom domain option for $100/month, which seemed rather expensive. So I decided to see if I could do this on my own. Vercel also lets you "alias" any domain to a review app. I'm happy to pay for things, so I can get on with my life, but this seemed too trivial.
tl;dr I used a Github Action to coordinate this.
Our situation
We had a small team of 5 engineers (for now). We wanted a system that:
- Automatically create deployments from pull-requests.
- Use a custom domain for said deployments to match our cookies.
- Was completely automatic.
So if my coworker made a pull request, they would get a custom instance of their site hosted at something.myapp.com
and it could talk to the backend server backend.myapp.com
.
DNS
The first step was DNS. For each engineer I created a DNS record github_username.myapp.dev
. I pointed these all to the Vercel cname, and then I created these as domains for my app in Vercel.
Ultimately these would be the deployment targets that we would hit.
Github Actions
Github has various events which it can listen to. The event we were listening to was called deployment_status
. Specifically we were waiting for the Vercel deployment to complete. At which point we would do our aliasing work.
Github let's you run jobs conditionally, this was our above condition as code:
if: |
github.event.deployment_status.state == 'success' &&
endsWith( github.event.deployment_status.target_url, '.vercel.app')
We had 4 general steps we wanted to do:
- Find the current user.
- Run the alias command (via the Vercel CLI)
- Create a new "deployment" in Github for our custom domain.
- Mark said deployment as successful.
Finding the current user
I found Github Actions to be pretty powerful, in the way that bash
is powerful... you can do whatever you want and there's no prescribed "right" way. So here's how I found the creator of a pull-request from a deployment_status
event:
- uses: octokit/request-action@v2.x
name: Find Author
id: get_author
with:
route: GET /repos/${{ github.repository }}/commits/${{ github.sha }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
This uses the Github API (which is well documented) to give you data about the commit that was deployed (github.sha
). From that we are able to get the author's Github username by doing something like:
${{ fromJson(steps.get_author.outputs.data).author.login }}
Running the alias command
- uses: emregency/vercel-preview-alias@v1.5
name: Make Alias
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
vercel-preview-url: ${{ github.event.deployment_status.target_url }} #Required
vercel-target-url: ${{ fromJson(steps.get_author.outputs.data).author.login }}.myapp.com #Required
This action calls the alias
command that you get from the vercel
CLI. It takes an existing preview URL which we get from the deployment_status
event that triggered this action and you give it your target URL. We are going with github_username.myapp.com
as mentioned above. It's very fast as almost everything in Vercel is.
Making a deployment and marking it as success
To really clean things up and potentially trigger other actions (e.g. Calibre) we create a new deployment and deployment status. This let's people get the new .myapp.com
link for their pull request:
- uses: octokit/request-action@v2.x
name: Create Deployment
id: deployment
with:
route: POST /repos/${{ github.repository }}/deployments
ref: ${{ github.sha }}
environment: Preview
auto_merge: false
description: Deploy to ${{ fromJson(steps.get_author.outputs.data).author.login }}.tome.fans
required_contexts: '[]'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: octokit/request-action@v2.x
name: Set Deployment Status
id: deployment_status
with:
route: POST /repos/${{ github.repository }}/deployments/${{ fromJSON(steps.deployment.outputs.data).id }}/statuses
state: success
target_url: https://${{ fromJson(steps.get_author.outputs.data).author.login }}.tome.fans
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
This makes two calls to the Github API, one to create a deployment, and a second to set it as successful.
This wraps up the code into a nicely executing workflow.
Sustainability
While it's kind of fun to spend lots of hours trying to save a few dollars, I felt like there were little parts of this work-flow that could go wrong later and I didn't want to maintain it. So I ultimately ended up paying for this service and deleting a file from our repository. If you looked closely, our work around provisioned one domain per engineer, which often wasn't enough for our team members who had many frontend pull requests.
While I really like Vercel, I'm not really a fan of the pricey upgrades. It's like buying a car and finding out that you need to pay an extra monthly fee to use a feature. If it really bothers me, I might restructure our app to not used.