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
- Two linked workspaces
Such as
acme-stagingandacme-production, withacme-staginglinked to promote toacme-production. An admin of each adds the link. See Linked workspaces. - Two API keys of the staging workspace
A developer key to deploy. An admin key that can only promote:
POST /v1/api-keyswith{"name": "github-promote", "role": "admin", "capabilities": ["release.promote"]}, made by an admin signed in to the dashboard or with a key that listsrelease.promoteitself. Both come fromacme-staging. Production needs no key. - Two GitHub environments
In the repository's Settings → Environments, create
stagingwith the secretSHIFTLABS_API_KEY(the developer key) andproductionwith the secretSHIFTLABS_PROMOTE_KEY(the promote key). Required reviewers onproductionare optional: production's own approval is the gate. - A project
A
shiftlabs.config.ts, with shiftlabs 0.4.0 or later inpackage.jsonand apackage-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.
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
productionpromotes the release thestagingjob deployed, by its number. That release's versions reach production byte for byte, whatever has been deployed to staging since.acme-productionapproves promotions by default. Withwait: truethe step waits for the answer and fails unless the release is applied: denied, unanswered within 72 hours, or refused when it's checked again. Itsstatusoutput says which. GitHub ends a job after 6 hours, so answer within that time. Withoutwait, the step succeeds once the request is made.- When a commit changes nothing,
deployrecords no release, and the action'sreleaseis 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
maininstead of tags, remove theif: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 inacme-productiondecide. - An admin of
acme-productioncan set the link todirect, so promotions apply without an approval there. Then whoever holds the promote key decides what code production runs, and GitHub's Required reviewers onproductionare 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.
| Name | For |
|---|---|
api-key | Required. A key of the workspace to deploy to, or to promote from. |
command | deploy (the default) or promote. |
release | promote: the release number, such as a deploy step's release output. |
to | promote: the linked workspace, such as acme-production. |
wait | promote: true waits for production's answer and fails the step unless the release is applied. Default false. |
config | deploy: the project file. Default shiftlabs.config.ts. |
working-directory | The folder with the project. Default the repository's root. |
cli-version | The CLI version npx runs when the project installs none. Default the version the action was published with. |
api-url | Default https://api.shiftlabs.org. |
output release | deploy: the release that holds the project. promote: the new release in the linked workspace, empty while it waits for approval. |
output workspace | The workspace that release is in. |
output status | promote: applied, waiting for approval, denied, expired or failed. Set on failure too, for steps that run then. |
output approval | promote: the approval request in the linked workspace, empty where promotions need none. |
# 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 }}