Skip to content
Latchkey

Deploy site (on tag) workflow (CarGuo/gsy_flutter_book)

The Deploy site (on tag) workflow from CarGuo/gsy_flutter_book, explained and optimized by Latchkey.

B

CI health: B - good

Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: CarGuo/gsy_flutter_book.github/workflows/deploy.ymlLicense MITView source

What it does

This is the Deploy site (on tag) workflow from the CarGuo/gsy_flutter_book repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Deploy site (on tag)

on:
  push:
    tags:
      - 'v*'
      - 'release-*'
  workflow_dispatch: {}

concurrency:
  group: deploy-site
  cancel-in-progress: false

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: write   # required to create a Release

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Chrome for Puppeteer
        run: pnpm exec puppeteer browsers install chrome

      - name: Install CJK fonts for PDF rendering
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y -qq fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-color-emoji
          fc-cache -f
          echo "--- installed CJK fonts ---"
          fc-list :lang=zh | head -n 20 || true

      - name: Run migration (SUMMARY.md -> docs/)
        run: pnpm run migrate

      - name: Build VitePress
        run: pnpm exec vitepress build docs

      - name: Build Pagefind index
        run: pnpm run search:index

      - name: Build full-site PDF
        timeout-minutes: 30
        run: pnpm run build:pdf

      - name: Show artifact size
        run: |
          du -sh docs/.vitepress/dist
          du -sh docs/.vitepress/dist/pagefind || true
          ls -lh artifacts/gsy-flutter-book.pdf || true

      - name: Setup SSH
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_SSH_PORT || 22 }}
          DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          # Normalize key: strip CR, drop trailing whitespace, ensure single trailing newline
          printf '%s\n' "$DEPLOY_SSH_KEY" | tr -d '\r' | sed -e 's/[[:space:]]*$//' > ~/.ssh/id_ed25519
          # Guarantee the file ends with exactly one newline
          tail -c1 ~/.ssh/id_ed25519 | od -An -c | grep -q '\\n' || printf '\n' >> ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          # Sanity check before use; fail fast with a readable message
          if ! ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub; then
            echo "::error::DEPLOY_SSH_KEY is not a valid OpenSSH private key."
            exit 1
          fi
          ssh-keyscan -H -p "${DEPLOY_PORT}" "${DEPLOY_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
          chmod 600 ~/.ssh/known_hosts

      - name: Deploy via rsync
        env:
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_SSH_PORT || 22 }}
        run: |
          set +e
          for attempt in 1 2 3 4 5; do
            echo "=== rsync attempt $attempt ==="
            rsync -avz --delete \
              -e "ssh -p ${DEPLOY_PORT} -o StrictHostKeyChecking=yes -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \
              docs/.vitepress/dist/ \
              "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"
            rc=$?
            if [ $rc -eq 0 ]; then
              echo "rsync succeeded on attempt $attempt"
              exit 0
            fi
            echo "rsync failed with code $rc, sleeping before retry..."
            sleep $((attempt * 20))
          done
          echo "::error::rsync failed after 5 attempts"
          exit 1

      - name: Create GitHub Release with PDF
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v2
        with:
          name: ${{ github.ref_name }}
          files: artifacts/gsy-flutter-book.pdf
          fail_on_unmatched_files: true

      - name: Summary
        run: |
          echo "Deployed tag ${GITHUB_REF_NAME}."

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: Deploy site (on tag)
 
on:
  push:
    tags:
      - 'v*'
      - 'release-*'
  workflow_dispatch: {}
 
concurrency:
  group: deploy-site
  cancel-in-progress: false
 
jobs:
  build-and-deploy:
    timeout-minutes: 30
    runs-on: latchkey-small
    permissions:
      contents: write   # required to create a Release
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 1
 
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
 
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
 
      - name: Install Chrome for Puppeteer
        run: pnpm exec puppeteer browsers install chrome
 
      - name: Install CJK fonts for PDF rendering
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y -qq fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-color-emoji
          fc-cache -f
          echo "--- installed CJK fonts ---"
          fc-list :lang=zh | head -n 20 || true
 
      - name: Run migration (SUMMARY.md -> docs/)
        run: pnpm run migrate
 
      - name: Build VitePress
        run: pnpm exec vitepress build docs
 
      - name: Build Pagefind index
        run: pnpm run search:index
 
      - name: Build full-site PDF
        timeout-minutes: 30
        run: pnpm run build:pdf
 
      - name: Show artifact size
        run: |
          du -sh docs/.vitepress/dist
          du -sh docs/.vitepress/dist/pagefind || true
          ls -lh artifacts/gsy-flutter-book.pdf || true
 
      - name: Setup SSH
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_SSH_PORT || 22 }}
          DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          # Normalize key: strip CR, drop trailing whitespace, ensure single trailing newline
          printf '%s\n' "$DEPLOY_SSH_KEY" | tr -d '\r' | sed -e 's/[[:space:]]*$//' > ~/.ssh/id_ed25519
          # Guarantee the file ends with exactly one newline
          tail -c1 ~/.ssh/id_ed25519 | od -An -c | grep -q '\\n' || printf '\n' >> ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          # Sanity check before use; fail fast with a readable message
          if ! ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub; then
            echo "::error::DEPLOY_SSH_KEY is not a valid OpenSSH private key."
            exit 1
          fi
          ssh-keyscan -H -p "${DEPLOY_PORT}" "${DEPLOY_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
          chmod 600 ~/.ssh/known_hosts
 
      - name: Deploy via rsync
        env:
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_SSH_PORT || 22 }}
        run: |
          set +e
          for attempt in 1 2 3 4 5; do
            echo "=== rsync attempt $attempt ==="
            rsync -avz --delete \
              -e "ssh -p ${DEPLOY_PORT} -o StrictHostKeyChecking=yes -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \
              docs/.vitepress/dist/ \
              "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"
            rc=$?
            if [ $rc -eq 0 ]; then
              echo "rsync succeeded on attempt $attempt"
              exit 0
            fi
            echo "rsync failed with code $rc, sleeping before retry..."
            sleep $((attempt * 20))
          done
          echo "::error::rsync failed after 5 attempts"
          exit 1
 
      - name: Create GitHub Release with PDF
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v2
        with:
          name: ${{ github.ref_name }}
          files: artifacts/gsy-flutter-book.pdf
          fail_on_unmatched_files: true
 
      - name: Summary
        run: |
          echo "Deployed tag ${GITHUB_REF_NAME}."
 

What changed

2 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow