Release by tagging on Vercel
Tags: vercel, github, cicd, webdev
Published: 2022-09-05 01:25:55

## Where is the problem?

I like to have my project configured to have a staging build on which I run all my acceptance tests, close to production configuration, just before the production release. 

To achieve it, my staging build represents the current `HEAD` of the project's `main` branch. After running, the acceptance tests branch is tagged and released to production.

The default configuration for the project on [vercel.com](https://vercel.com) releases the main branch to production with every commit. It is easy to configure the second branch for staging build, by adding `staging` branch and domain configuration for it: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a3sxg9i0zit5hzgbjc4t.png)

In this configuration, the head of the `main` branch will be released to https://vercel-release-by-tag.vercel.app/, and the changes of the `staging` branch will be released to `https://staging-vercel-release-by-tag.vercel.app/`

In order to release to production, one needs to merge the `staging` branch to `main`.

This is an acceptable workflow, but you are not using the GitHub release page to keep track of changes between different production releases. Additionally, developers need to manage two branches, which is not optimal.

## First try

My first approach to solving problem with tagged releases was to use [can you deploy based on tags releases on vercel](https://vercel.com/guides/can-you-deploy-based-on-tags-releases-on-vercel) guide. The proposed solution - is to create an unused branch and [deploy hook](https://vercel.com/docs/concepts/git/deploy-hooks) to release this tag, which did not work for me. The deploy hooks are linked to the branch:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/onyo18r4dvoylc2br6da.png)

And because of that link, they can release only code which is on a specific branch. Configuring a webhook to release the `staging` branch will release that branch to the staging environment, I didn't find a way to change that behaviour.

## Second try

My second try to solve it was to move release to GitHub actions with [how can I use GitHub Actions with Vercel?]
(https://vercel.com/guides/how-can-i-use-github-actions-with-vercel) guide.

As a result i created `.github/workflows/deploy-prod.yml` file with content:

```
name: Deploy to production
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
  push:
    tags:
      - 'v*'

jobs:
  Deploy-Production:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: install and release
        run: | 
          npm install --global vercel@latest
          vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
          vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
          vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
```
This approach was not acceptable:
 * Build was done once in GitHub Action worker and build artefact need to be uploaded to Vercel, and this process was slower than building directly there.
 * Prisma client was not generated correctly without additional configuration, because the build environment was different than the runtime.
The pros of this approach were it doesn't require creating an artificial branch. 

## Final approach

For the final solution, I decided to configure Vercel to build directly from the branch, but add workflow to make sure all the changes on the production branch are done automatically after applying the tag. 

To achieve it I created [production](https://github.com/dswistowski/vercel-release-by-tag/tree/production) branch in my git repo and created for both `main` and `production` protection rules:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jcypdd4191tgdrfmpibx.png).
Protection makes sure the `production` branch will be not deleted by accident after merging it to a different branch.

After that, I configured Vercel to release production code from the `production` branch (settings -> git)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wybknmcfuoumfbh6fcud.png)

And to release `main` branch to `staging` domain (settings->domain):

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ev58dmun5w2stigcu92l.png)

And the final piece is to update `production` branch on tag creation. The easies way is to create GitHub Action:
```
name: Deploy to production
on:
  push:
    tags:
      - 'v*'

jobs:
  Deploy-Production:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Release to production
        run: |
          git push origin HEAD:production -f
```
`git push origin HEAD:production -f` command will force an update current `production` branch with the currently tagged commit (because the action is executed only on tags starting with `v`.

### Created process:
![workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0pz7oaajitjzaub2v9f.png)

## Closing thoughts

This solution assumes workflow when the team works on the `main` branch and the `production` is updated only by action after tagging. It's possible to lock the ability to update the `production` branch by accident by adding a pull request requirement to the branch protection rule, creating a bot account with `bypass branch protections` right for this branch and using the token of that account in GitHub action to authorise git access. 

The second way of defence against accidental release of production is to configure [ignored build step](https://vercel.com/docs/concepts/projects/overview#ignored-build-step).

Script similar to:
```
#!/bin/bash

echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF"

if [[ "$VERCEL_GIT_COMMIT_REF" == "main" ]]; then
  echo "✅ - Staging build can proceed"
  exit 1;
elif [[ "$VERCEL_GIT_COMMIT_REF" == "production" && $(git name-rev --name-only --tags HEAD) != "undefined"  ]]; then
  echo "✅ - Production build can proceed"
  exit 1;
else
  # Don't build
  echo "🛑 - Build cancelled"
  exit 0;
fi
```