DEV Community

nausaf
nausaf

Posted on

Environments in GitHub (with example of Next.js deployment to Vercel)

An environment is basically a set of three things:

  • Secrets
  • Variables
  • Protection rules

When you define an environment in GitHub, you provide a name and can configure any of the above.

For example, when defining an environment named UAT (in Environments tab in repo Settings), the environment definition page would look like this:

Image description

What is interesting is that you can reference an environment in a job using an environment block like this:

jobs:
  deploy-to-vercel-pr-preview-env:
    runs-on: ubuntu-24.04
    environment:
      name: Preview
      url: ${{ steps.deploy-artifacts.outputs.previewUrl }}
Enter fullscreen mode Exit fullscreen mode

In addition to the fact that such a job is allowed to access Variables and Secrets defined within the environment (as opposed to at the repo level) and Protection Rules such as delayed execution, manual approval, and deployment only from branches meeting specified criteria (e.g. with matching names and/or with branch protection rules) apply, when a job references an environment, this enables a number of other interesting behaviours:

  • There is a really nice sticky comment on the PR (so it updates with every push to the source branch if that triggers the CI workflow) which shows the value of the url property for the environment from the latest run of the job that references the environment:

    Image description

  • The url property of environment can be static but it can also reference a step output parameter which can even be from a step within the same job. In the latter case it will be evaluated after the step within the job that outputs that parameter value has executed. In the job snippet shown above, the step that produces the referenced step output parameter is:

    id: deploy-artifacts
        run: |
          previewUrl=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
          echo "previewUrl=$previewUrl" >> "$GITHUB_OUTPUT"
    
  • If a job referencing an environment runs on multiple source branches (in multiple PRs), they don't seem to wait for each other i.e. they seem to run in parallel (I might be wrong on this). In this case each would post a different URL in its sticky comment (assuming we are deploying each PR's source branch to a different URL, as happens when you deploy to Preview environment - this is an environment in a Vercel project as opposed to in GitHub repo for the project - in which case multiple deployments would be listed for that environment and each would be acessible!).

  • All deployments to an environment, with their respective URLs if provided, can also be seen on repo main page in a section on right hand side named Deployments:

    Image description

    You can click an environment name and see a list of all deployments to it, including link to each. The link to the most recent deployment is shown right at the top:

    Image description

When using environments, my CI workflow deploys to Vercel's Preview environment and references GitHub repo environment of the same name, and the CD/release workflow deployment job references the GitHub Production envronment.

Full CI workflow (ci.yml) as follows:

name: CI
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
permissions:
  checks: write
  pull-requests: write
on:
  pull_request:
    branches:
      - main
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  deploy-to-vercel-pr-preview-env:
    runs-on: ubuntu-24.04
    environment:
      name: Preview
      url: ${{ steps.deploy-artifacts.outputs.previewUrl }}
    steps:
      - uses: actions/checkout@v2
      - name: Update Version Number in package.json
        run: npm --no-git-tag-version version 0.0.0-pr${{ github.event.number }}
      - name: Install Vercel CLI
        run: npm install --global vercel@latest
      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build Project Artifacts
        run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy Project Artifacts to Vercel
        id: deploy-artifacts
        run: |
          previewUrl=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
          echo "previewUrl=$previewUrl" >> "$GITHUB_OUTPUT"

Enter fullscreen mode Exit fullscreen mode

Release workflow that deploys to Production is as follows:

name: Release to Production
concurrency: release-to-prod-pipeline
on:
  push:
    branches:
      - main

jobs:
  create-release:
    # UNCOMMENT THE FOLLOWING LINE WHEN
    # you have defined jobs named
    # 'build' and 'test'
    #
    # SEMANTIC-RELEASE SHOULD ONLY RUN
    # AFTER TESTS HAVE RUN SUCCESSFULLY
    #
    #needs: [build, test]

    # This permissions block wasn't needed in
    # earlier versions of semantic-release
    # (I have a project currently using an
    # earlier version and it works fine without
    # a permissions block anywhere in the workflow)
    # The issue is described here:
    # https://github.com/semantic-release/semantic-release/issues/2481
    permissions:
      contents: write
    runs-on: ubuntu-24.04
    name: Create Release
    outputs:
      released: ${{ env.RELEASED }}
      newVersion: ${{ env.NEW_VERSION }}
    steps:
      - uses: actions/checkout@v3
        name: Checkout code
        id: checkout
        with:
          submodules: true

      - name: Create GitHub release
        id: semanticrelease
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
        run: |
          npm ci
          echo "RELEASED=0" >> $GITHUB_ENV
          npm run release

  deploy:
    name: Deploy to Vercel
    runs-on: ubuntu-24.04
    env:
      VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
      VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
    needs: create-release
    if: ${{ needs.create-release.outputs.released == 1}}
    steps:
      - name: Show version
        run: |
          echo "New Version Number is: ${{ needs.create-release.outputs.newVersion }}"
      - uses: actions/checkout@v2
      - name: Update Version Number in package.json
        run: npm --no-git-tag-version version ${{ needs.create-release.outputs.newVersion }}
      - name: Install Vercel CLI
        run: npm install --global vercel@latest
      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy Project Artifacts to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)