Automations Anonymous
Sheet
Display
Read
Type

Sheet 04 · /automations/build-test-and-deploy-on-every-push

Build, test and deploy on every push to main

A GitHub Actions workflow installs, tests and builds on every push to main and then runs one publish step, cancelling an older run when a newer push arrives.

Difficulty:
intermediate

Problem

Deploying by hand from a laptop means the deployed thing was built on that laptop, with whatever happened to be installed that day, and two people deploying at once race each other.

Trigger

A push to the main branch.

Prerequisites

  • 01A repository on GitHub with Actions enabled.
  • 02A package.json with a build script. Tests are optional: --if-present skips cleanly when there are none.
  • 03A deploy credential that can be revoked on its own without rotating anything else.

Steps

  1. 01

    Save the workflow at .github/workflows/deploy.yml github-actions

  2. 02

    Put the deploy credential in repository secrets as DEPLOY_TOKEN github-actions

    Settings > Secrets and variables > Actions. Never in the workflow file.

  3. 03

    Write scripts/publish.sh so it is the only step that touches production

    Keeping the publish in one script means it can still be run by hand in an emergency.

  4. 04

    Push a trivial change and watch the run github-actions

    The Actions tab shows every step. A green run that skipped the tests is not a green run: check that npm test actually ran.

Payload · yaml

name: Deploy
on:
  push:
    branches: [main]

# One deploy at a time. A newer push cancels an older run.
concurrency:
  group: deploy
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm test --if-present
      - run: npm run build
      - name: Publish
        run: ./scripts/publish.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Failure modes

  • !cancel-in-progress cancels a run that was already mid publish. Make publish.sh safe to run twice, or drop the concurrency block.
  • !npm ci fails when package-lock.json is out of date with package.json. Commit the lockfile.
  • !The build passes on the runner and fails on a developer machine, or the reverse. The runner is the source of truth from now on.
  • !A secret is only masked in logs when it matches exactly. Do not echo credentials, even partially.

JSON-LD · HowTo

{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Build, test and deploy on every push to main",
  "description": "A GitHub Actions workflow installs, tests and builds on every push to main and then runs one publish step, cancelling an older run when a newer push arrives.",
  "url": "https://automationsanonymous.com/automations/build-test-and-deploy-on-every-push",
  "tool": [
    {
      "@type": "HowToTool",
      "name": "github-actions",
      "url": "https://automationsanonymous.com/tools/github-actions"
    },
    {
      "@type": "HowToTool",
      "name": "npm",
      "url": "https://automationsanonymous.com/tools/npm"
    }
  ],
  "step": [
    {
      "@type": "HowToStep",
      "position": 1,
      "name": "Save the workflow at .github/workflows/deploy.yml",
      "text": "Save the workflow at .github/workflows/deploy.yml",
      "url": "https://automationsanonymous.com/tools/github-actions"
    },
    {
      "@type": "HowToStep",
      "position": 2,
      "name": "Put the deploy credential in repository secrets as DEPLOY_TOKEN",
      "text": "Settings > Secrets and variables > Actions. Never in the workflow file.",
      "url": "https://automationsanonymous.com/tools/github-actions"
    },
    {
      "@type": "HowToStep",
      "position": 3,
      "name": "Write scripts/publish.sh so it is the only step that touches production",
      "text": "Keeping the publish in one script means it can still be run by hand in an emergency."
    },
    {
      "@type": "HowToStep",
      "position": 4,
      "name": "Push a trivial change and watch the run",
      "text": "The Actions tab shows every step. A green run that skipped the tests is not a green run: check that npm test actually ran.",
      "url": "https://automationsanonymous.com/tools/github-actions"
    }
  ],
  "datePublished": "2026-09-05T16:53:27.173Z"
}