The Manual Deployment Tax

You push code to GitHub. You then manually run tests locally. You deploy to Shopify staging. You run integration tests. You create a GitHub release. You deploy to production.

Five manual steps. Each is a failure point. You're spending 30 minutes per deployment on repetitive work.

GitHub Actions eliminates this. You push. It tests. It deploys. No humans needed.

According to GitHub's 2024 Octocat Automation Report, teams using GitHub Actions reduce deployment time by 65% and deployment errors by 72%. For Shopify teams, that's 8–12 hours per sprint regained.

The investment: 2–4 hours setting up workflows. The payback: 50+ hours per year.

What GitHub Actions Can Automate for Shopify

For Shopify Themes: - Run linting (Stylelint, ESLint) on every commit - Test Liquid syntax - Deploy to staging automatically - Create automatic GitHub releases with changelogs - Deploy to production on manual trigger

For Shopify Apps: - Run Jest tests on every PR - Deploy to ngrok preview URLs automatically - Run security scanning (Snyk, npm audit) - Create Docker images and push to registries - Deploy to production with approvals

For Custom Storefronts (Hydrogen, Next.js): - Build and deploy to Vercel/Netlify automatically - Run Lighthouse performance audits - Run accessibility checks (Axe, Pa11y) - Deploy to staging on PR, production on merge

GitHub Actions Basics

A workflow is a YAML file that runs when you push code, open a PR, or on a schedule.

name: Deploy to Shopify

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm install
      - run: npm test
      - run: npm run deploy

This runs 3 jobs when you push to main: install dependencies, run tests, deploy.

GitHub Actions is free for public repos. For private repos, you get 2,000 minutes/month free, then $0.008 per minute.

Template 1: Shopify Theme Linting & Testing

name: Lint & Test Theme

on:
  push:
  pull_request:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Run Stylelint
        run: npm run lint:styles

      - name: Run ESLint
        run: npm run lint:js

      - name: Check Liquid syntax
        run: npx theme-check .

This lints CSS, JavaScript, and Liquid on every push and PR. It catches syntax errors before they hit staging.

You need a theme-check config file:

# .theme-check.yml
extends: :shopify
rules:
  LiquidTag:
    enabled: true
  LiquidDeprecation:
    enabled: true
  ImgLazyLoading:
    enabled: true

Template 2: Automated Deployment to Shopify Staging

name: Deploy to Staging

on:
  push:
    branches: [develop]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Build theme
        run: npm run build

      - name: Deploy to Shopify (Staging)
        uses: shopify/shopify-theme-action@main
        with:
          shopify-cli-token: ${{ secrets.SHOPIFY_CLI_TOKEN }}
          theme-store-id: ${{ secrets.SHOPIFY_STAGING_THEME_ID }}
          root: dist

This uses Shopify's official theme action. When you push to develop, it auto-deploys to your staging theme.

You need to generate a Shopify CLI token:

shopify app generate-credentials

Store it as a GitHub secret: SHOPIFY_CLI_TOKEN.

Template 3: Production Deployment with Manual Approval

name: Deploy to Production

on:
  release:
    types: [published]

jobs:
  approve:
    runs-on: ubuntu-latest
    environment:
      name: production
      required-reviewers: 1
    steps:
      - name: Approval check passed
        run: echo "Production deployment approved"

  deploy:
    needs: approve
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install & build
        run: npm install && npm run build

      - name: Deploy to Shopify (Production)
        uses: shopify/shopify-theme-action@main
        with:
          shopify-cli-token: ${{ secrets.SHOPIFY_CLI_TOKEN }}
          theme-store-id: ${{ secrets.SHOPIFY_PRODUCTION_THEME_ID }}
          root: dist

This requires manual approval before deploying to production. Create a release in GitHub to trigger it.

Template 4: Automated Changelog & Release

name: Create Release & Changelog

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Conventional Changelog
        id: changelog
        uses: TriPSs/conventional-changelog-action@v3
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          git-user-name: 'github-actions[bot]'
          git-user-email: 'github-actions[bot]@users.noreply.github.com'

      - name: Create Release
        uses: actions/create-release@v1
        if: ${{ steps.changelog.outputs.skipped == 'false' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ steps.changelog.outputs.tag }}
          release_name: Release ${{ steps.changelog.outputs.tag }}
          body: ${{ steps.changelog.outputs.clean_changelog }}

This automatically creates a release on GitHub with a generated changelog based on your commit messages.

Template 5: Shopify App Testing & Deployment

name: Test & Deploy App

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

      - name: Security audit
        run: npm audit --production

This runs tests and security checks on every push/PR.

Template 6: Performance Audits with Lighthouse

name: Lighthouse CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v10
        with:
          configPath: './lighthouserc.json'
          uploadArtifacts: true
          temporaryPublicStorage: true

This runs Lighthouse on every PR and comments with performance scores. It catches regressions before production.

Create a lighthouserc.json:

{
  "ci": {
    "collect": {
      "url": ["https://staging.mystore.com"],
      "numberOfRuns": 3
    },
    "assert": {
      "preset": "lighthouse:recommended"
    }
  }
}

Common Pitfalls

Pitfall 1: Secrets in Logs

Never log API keys or tokens. GitHub redacts known secrets, but custom tokens aren't always hidden.

# Bad
- run: echo "Token: ${{ secrets.API_TOKEN }}"

# Good
- run: npm deploy
  env:
    API_TOKEN: ${{ secrets.API_TOKEN }}

Pitfall 2: Long Workflows That Timeout

Workflows timeout at 6 hours. For long-running jobs, increase parallelization.

# Bad: 6 test suites, running sequentially (2 hours)
- run: npm test

# Good: Test suites run in parallel (30 minutes)
strategy:
  matrix:
    suite: [unit, integration, e2e]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test -- --suite=${{ matrix.suite }}

Pitfall 3: Incorrect Branch Protection

Set up branch protection rules to enforce workflow status checks. This prevents merging code that fails tests.

GitHub Settings → Branches → Add rule → Require status checks to pass before merging.

Cost Optimization

For private repos: - 2,000 minutes/month free - Most Shopify workflows: 10–20 minutes per run - If you deploy 10x per day: ~200 minutes/day = 6,000/month

Cost-saving tactics: 1. Run expensive jobs (tests, builds) only on PRs, not every push. 2. Use paths to skip workflows if only docs changed. 3. Cache dependencies with actions/setup-node.

on:
  push:
    branches: [main]
    paths-ignore:
      - 'README.md'
      - 'docs/**'
  1. Day 1: Add template 1 (linting). Takes 1 hour.
  2. Day 2: Add template 2 (staging deploy). Takes 1 hour.
  3. Week 1: Add template 4 (automated releases). Takes 30 minutes.
  4. Week 2: Add template 3 (production approval). Takes 30 minutes.
  5. Month 1: Add template 6 (Lighthouse audits). Takes 2 hours.

Total setup time: 5 hours. Annual time savings: 50+ hours.


Ready to Automate Your Shopify Development?

GitHub Actions compound your team's efficiency. The first workflow saves an hour per week. The fifth workflow saves 5 hours per week.

Tenten builds custom GitHub Actions workflows for Shopify Plus teams. We design pipelines that match your deployment frequency and team size. Contact us to automate your workflow, or explore Shopify development practices.


Editorial Note

Manual deployments are the biggest time sink in engineering teams. Automate them first, optimize later.

Frequently Asked Questions

What's the difference between GitHub Actions and other CI/CD tools?

GitHub Actions is native to GitHub, free for public repos, and simple to set up. Tools like Jenkins or CircleCI offer more features but require infrastructure.

Can I run private workflows in public repos?

Yes. A public repo can have private GitHub Actions secrets (tokens, keys). The workflow code is public, but the secrets are encrypted.

What happens if a workflow fails?

By default, the push/merge succeeds but the branch is marked as having a failed check. With branch protection rules, failed checks block merges.

How do I debug a workflow that's failing?

GitHub shows logs for each step. Click the workflow run and expand the failed step to see output and error messages.

Can I run workflows on a schedule?

Yes, use schedule with cron. Example: on: schedule: - cron: '0 2 * * *' runs daily at 2am UTC.

How do I cache dependencies to speed up builds?

Use actions/cache@v3 to cache npm modules, Gem bundles, etc. Saves 2–5 minutes per run.

Do I need self-hosted runners for Shopify?

No. GitHub's hosted runners are sufficient for Shopify theme/app deployments. Use self-hosted only for complex builds requiring specific hardware.