shiftlabs Start free
Menu Deploy from GitHub

Deploy from GitHub

Every push to main deploys to staging. A tag promotes that exact release to production, once someone in production approves it.

Before you start

  1. Two linked workspaces

    Such as acme-staging and acme-production, with acme-staging linked to promote to acme-production. An admin of each adds the link. See Linked workspaces.

  2. Two API keys of the staging workspace

    A developer key to deploy. An admin key that can only promote: POST /v1/api-keys with {"name": "github-promote", "role": "admin", "capabilities": ["release.promote"]}, made by an admin signed in to the dashboard or with a key that lists release.promote itself. Both come from acme-staging. Production needs no key.

  3. Two GitHub environments

    In the repository's Settings → Environments, create staging with the secret SHIFTLABS_API_KEY (the developer key) and production with the secret SHIFTLABS_PROMOTE_KEY (the promote key). Required reviewers on production are optional: production's own approval is the gate.

  4. A project

    A shiftlabs.config.ts, with shiftlabs 0.4.0 or later in package.json and a package-lock.json. The action runs the CLI your lockfile pins. See Projects.

The workflow

Commit this file. A push to main deploys to acme-staging. A tag such as v1.4.0 deploys that commit to acme-staging too, then asks acme-production to take the release. The job waits until someone in acme-production approves or denies it on its Approvals screen.

yaml.github/workflows/shiftlabs.yml
name: shiftlabs

on:
  push:
    branches: [main]
    tags: ["v*"]

jobs:
  staging:
    runs-on: ubuntu-latest
    environment: staging
    outputs:
      release: ${{ steps.deploy.outputs.release }}
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
      - run: npm ci
      - id: deploy
        uses: Adaptive-Technology-Solutions/shiftlabs-action@v1
        with:
          api-key: ${{ secrets.SHIFTLABS_API_KEY }}

  production:
    if: startsWith(github.ref, 'refs/tags/v')
    needs: staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
      - run: npm ci
      - uses: Adaptive-Technology-Solutions/shiftlabs-action@v1
        with:
          command: promote
          release: ${{ needs.staging.outputs.release }}
          to: acme-production
          api-key: ${{ secrets.SHIFTLABS_PROMOTE_KEY }}
          wait: true
  • production promotes the release the staging job deployed, by its number. That release's versions reach production byte for byte, whatever has been deployed to staging since.
  • acme-production approves promotions by default. With wait: true the step waits for the answer and fails unless the release is applied: denied, unanswered within 72 hours, or refused when it's checked again. Its status output says which. GitHub ends a job after 6 hours, so answer within that time. Without wait, the step succeeds once the request is made.
  • When a commit changes nothing, deploy records no release, and the action's release is the release that already has these versions. If none of the last 20 does, the step fails: deploy a change, or promote a release by its number.
  • To promote every push to main instead of tags, remove the if: line. Each push then waits for an approval in production.
  • A promotion refused for a missing secret, var or connection fails the step with the names to add to production. Nothing changes in production.
  • The promote key can't deploy, and the deploy key can't promote. Neither is a key of acme-production: the promote key asks it to take a release, and people in acme-production decide.
  • An admin of acme-production can set the link to direct, so promotions apply without an approval there. Then whoever holds the promote key decides what code production runs, and GitHub's Required reviewers on production are the only gate.

The action

Adaptive-Technology-Solutions/shiftlabs-action@v1 runs shiftlabs deploy or shiftlabs promote with --json. It uses the CLI in node_modules/.bin of the working directory or a folder above it, else npx shiftlabs@<cli-version>. It needs shiftlabs 0.4.0 or later, the first version with promote, and fails the step on an older one. It needs Node 22.18 or later, which actions/setup-node provides.

Inputs and outputs
NameFor
api-keyRequired. A key of the workspace to deploy to, or to promote from.
commanddeploy (the default) or promote.
releasepromote: the release number, such as a deploy step's release output.
topromote: the linked workspace, such as acme-production.
waitpromote: true waits for production's answer and fails the step unless the release is applied. Default false.
configdeploy: the project file. Default shiftlabs.config.ts.
working-directoryThe folder with the project. Default the repository's root.
cli-versionThe CLI version npx runs when the project installs none. Default the version the action was published with.
api-urlDefault https://api.shiftlabs.org.
output releasedeploy: the release that holds the project. promote: the new release in the linked workspace, empty while it waits for approval.
output workspaceThe workspace that release is in.
output statuspromote: applied, waiting for approval, denied, expired or failed. Set on failure too, for steps that run then.
output approvalpromote: the approval request in the linked workspace, empty where promotions need none.
yaml.github/workflows/shiftlabs.yml
      # In the staging job, instead of the action:
      - id: deploy
        run: |
          npx shiftlabs deploy --json > deploy.json
          echo "release=$(jq -r '.release.seq // .current' deploy.json)" >> "$GITHUB_OUTPUT"
        env:
          SHIFTLABS_API_KEY: ${{ secrets.SHIFTLABS_API_KEY }}

      # In the production job:
      - run: npx shiftlabs promote "$RELEASE" --to acme-production --wait
        env:
          RELEASE: ${{ needs.staging.outputs.release }}
          SHIFTLABS_API_KEY: ${{ secrets.SHIFTLABS_PROMOTE_KEY }}