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.
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.
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
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
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Dependency installs
- End-to-end and browser tests
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.