Files
asepharyana-hub/docs/superpowers/plans/2026-05-27-production-github-actions.md
T

13 KiB

Production GitHub Actions Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Rework GitHub Actions build/deploy workflows into a production baseline with reliable React submodule updates, least-privilege permissions, clear deploy behavior, and current official action versions.

Architecture: Keep two workflows: docker-build-push.yml for detect/build/manifest updates, and deploy-docker.yml for VPS deployment. Add dispatch submodule SHA readiness checks before parent pointer updates, and remove recursive submodule checkout from deploy runner.

Tech Stack: GitHub Actions YAML, GitHub-hosted Ubuntu runners, Docker Buildx, GHCR, git submodules, Docker Compose over SSH.


File Structure

  • Modify .github/workflows/docker-build-push.yml: add default permissions, validate repository dispatch payloads, wait for submodule SHAs, keep selective matrix builds, harden manifest update.
  • Modify .github/workflows/deploy-docker.yml: add default permissions, remove recursive checkout, keep auto deploy from successful build, make deploy logs clearer.
  • Modify .github/dependabot.yml: add GitHub Actions update config so official actions stay current.

Task 1: Harden build workflow permissions and dispatch validation

Files:

  • Modify: .github/workflows/docker-build-push.yml

  • Step 1: Add workflow-level read permissions

At top level, after concurrency, add:

permissions:
  contents: read

Expected shape:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: false

permissions:
  contents: read

env:
  REGISTRY: ghcr.io
  • Step 2: Replace dispatch parser with payload validation

In .github/workflows/docker-build-push.yml, replace Parse repository_dispatch payload step body with:

      - name: Parse repository_dispatch payload
        id: dispatch
        if: github.event_name == 'repository_dispatch'
        env:
          SERVICE: ${{ github.event.client_payload.service }}
          SHA: ${{ github.event.client_payload.sha }}
        run: |
          set -euo pipefail

          if [ -z "${SERVICE:-}" ]; then
            echo "::error::repository_dispatch payload missing service"
            exit 1
          fi

          if [ -z "${SHA:-}" ]; then
            echo "::error::repository_dispatch payload missing sha"
            exit 1
          fi

          case "$SERVICE" in
            rust-api|elysia-api|react-web|9router) ;;
            *)
              echo "::error::Unsupported service '$SERVICE'. Expected one of: rust-api, elysia-api, react-web, 9router"
              exit 1
              ;;
          esac

          case "$SHA" in
            *[!0-9a-fA-F]*|???????????????????????????????????????|?????????????????????????????????????????*)
              echo "::error::Invalid sha '$SHA'. Expected 40 hex characters"
              exit 1
              ;;
          esac

          declare -a SERVICES=("rust-api" "elysia-api" "react-web" "9router")
          for svc in "${SERVICES[@]}"; do
            if [ "$SERVICE" = "$svc" ]; then
              echo "${svc}=true" >> "$GITHUB_OUTPUT"
            else
              echo "${svc}=false" >> "$GITHUB_OUTPUT"
            fi
          done
  • Step 3: Run YAML syntax check

Run:

python - <<'PY'
from pathlib import Path
import yaml
for path in Path('.github/workflows').glob('*.yml'):
    yaml.safe_load(path.read_text())
    print(f'OK {path}')
PY

Expected:

OK .github/workflows/deploy-docker.yml
OK .github/workflows/docker-build-push.yml
  • Step 4: Commit
git add .github/workflows/docker-build-push.yml
git commit -m "ci: validate dispatch payloads"

Task 2: Add submodule SHA readiness wait before build/update

Files:

  • Modify: .github/workflows/docker-build-push.yml

  • Step 1: Add readiness job after changes job

Insert this job between changes and build:

  wait-submodule-ref:
    needs: [changes]
    if: github.event_name == 'repository_dispatch'
    runs-on: ubuntu-latest
    steps:
      - name: Wait for submodule ref
        env:
          SERVICE: ${{ github.event.client_payload.service }}
          SHA: ${{ github.event.client_payload.sha }}
        run: |
          set -euo pipefail

          case "$SERVICE" in
            "rust-api") REPO="https://github.com/MythEclipse/ultimate-asepharyana-tech-rust.git" ;;
            "elysia-api") REPO="https://github.com/MythEclipse/ultimate-asepharyana-tech-elysia.git" ;;
            "react-web") REPO="https://github.com/MythEclipse/ultimate-asepharyana-tech-react.git" ;;
            "9router") REPO="https://github.com/MythEclipse/9router.git" ;;
            *)
              echo "::error::Unsupported service '$SERVICE'"
              exit 1
              ;;
          esac

          echo "Waiting for $SERVICE ref $SHA in $REPO"
          for attempt in {1..30}; do
            if git ls-remote --exit-code "$REPO" "$SHA" >/dev/null 2>&1; then
              echo "Submodule ref $SHA is fetchable for $SERVICE"
              exit 0
            fi
            echo "Attempt $attempt/30: $SHA not visible yet; waiting 10s"
            sleep 10
          done

          echo "::error::Submodule ref $SHA for $SERVICE was not fetchable after 300s"
          exit 1
  • Step 2: Make build wait for readiness job without blocking push/manual events

Change build job header from:

  build:
    needs: [changes]

to:

  build:
    needs: [changes, wait-submodule-ref]
    if: |
      always() &&
      needs.changes.result == 'success' &&
      (needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') &&
      needs.changes.outputs.matrix != '[]'

Remove existing build-level line:

    if: needs.changes.outputs.matrix != '[]'
  • Step 3: Make update-manifest wait for readiness job

Change update-manifest header from:

  update-manifest:
    needs: [changes, build]
    if: |
      always() &&
      (needs.build.result == 'success' || needs.build.result == 'skipped')

to:

  update-manifest:
    needs: [changes, wait-submodule-ref, build]
    if: |
      always() &&
      needs.changes.result == 'success' &&
      (needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') &&
      (needs.build.result == 'success' || needs.build.result == 'skipped')
  • Step 4: Run YAML syntax check

Run same command from Task 1 Step 3.

Expected both workflow files print OK.

  • Step 5: Commit
git add .github/workflows/docker-build-push.yml
git commit -m "ci: wait for submodule refs before builds"

Task 3: Harden manifest update and submodule checkout

Files:

  • Modify: .github/workflows/docker-build-push.yml

  • Step 1: Add job permissions to build and manifest jobs

Ensure build job contains:

    permissions:
      contents: read
      packages: write

Ensure update-manifest job contains:

    permissions:
      contents: write
  • Step 2: Replace dispatch submodule checkout block

Inside Update tags and submodules, replace the repository_dispatch submodule update block with:

              if [ "${{ github.event_name }}" == "repository_dispatch" ] && [ "${{ github.event.client_payload.service }}" == "$id" ]; then
                SHA_DISPATCH="${{ github.event.client_payload.sha }}"
                SUB_PATH="${PATHS[$id]}"
                if [ -n "$SHA_DISPATCH" ]; then
                  echo "Updating submodule $SUB_PATH to $SHA_DISPATCH"
                  git submodule update --init "$SUB_PATH"
                  git -C "$SUB_PATH" fetch origin "$SHA_DISPATCH"
                  git -C "$SUB_PATH" checkout "$SHA_DISPATCH"
                  git add "$SUB_PATH"
                  CHANGED=true
                fi
              fi
  • Step 3: Add pull/rebase retry before push

Replace:

            git commit -m "chore: update manifests and submodules [skip ci]"
            git pull --rebase origin main
            git push origin main

with:

            git commit -m "chore: update manifests and submodules [skip ci]"

            for attempt in {1..3}; do
              if git pull --rebase origin main && git push origin main; then
                exit 0
              fi
              echo "Manifest push attempt $attempt/3 failed; retrying"
              git rebase --abort || true
              git pull --rebase origin main || true
              sleep 5
            done

            echo "::error::Failed to push manifest update after 3 attempts"
            exit 1
  • Step 4: Run YAML syntax check

Run same command from Task 1 Step 3.

Expected both workflow files print OK.

  • Step 5: Commit
git add .github/workflows/docker-build-push.yml
git commit -m "ci: harden manifest updates"

Task 4: Make deploy checkout submodule-free and least privilege

Files:

  • Modify: .github/workflows/deploy-docker.yml

  • Step 1: Add workflow-level read permissions

After concurrency, add:

permissions:
  contents: read

Expected shape:

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

permissions:
  contents: read

If current cancel-in-progress is true, change it to false.

  • Step 2: Make checkout non-recursive

Replace checkout step:

      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          submodules: recursive

with:

      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1
          submodules: false
  • Step 3: Add deploy context log

At start of Deploy with Docker Compose on VPS run script, after set -euo pipefail, add:

          echo "Deploy event: ${{ github.event_name }}"
          echo "Deploy ref: ${{ github.ref }}"
          echo "Deploy sha: ${{ github.sha }}"
  • Step 4: Run YAML syntax check

Run same command from Task 1 Step 3.

Expected both workflow files print OK.

  • Step 5: Commit
git add .github/workflows/deploy-docker.yml
git commit -m "ci: avoid submodule checkout during deploy"

Task 5: Add GitHub Actions Dependabot updates

Files:

  • Modify: .github/dependabot.yml

  • Step 1: Add github-actions ecosystem

Append this update entry under updates::

  - package-ecosystem: 'github-actions'
    directory: '/'
    schedule:
      interval: weekly
    groups:
      github-actions:
        patterns:
          - '*'

Expected file shape:

version: 2
updates:
  - package-ecosystem: 'devcontainers'
    directory: '/'
    schedule:
      interval: weekly

  - package-ecosystem: 'github-actions'
    directory: '/'
    schedule:
      interval: weekly
    groups:
      github-actions:
        patterns:
          - '*'
  • Step 2: Run YAML syntax check

Run:

python - <<'PY'
from pathlib import Path
import yaml
paths = [Path('.github/dependabot.yml'), *Path('.github/workflows').glob('*.yml')]
for path in paths:
    yaml.safe_load(path.read_text())
    print(f'OK {path}')
PY

Expected:

OK .github/dependabot.yml
OK .github/workflows/deploy-docker.yml
OK .github/workflows/docker-build-push.yml
  • Step 3: Commit
git add .github/dependabot.yml
git commit -m "ci: enable github actions dependency updates"

Task 6: Final validation

Files:

  • Validate: .github/workflows/docker-build-push.yml

  • Validate: .github/workflows/deploy-docker.yml

  • Validate: .github/dependabot.yml

  • Step 1: Run YAML syntax check

Run:

python - <<'PY'
from pathlib import Path
import yaml
paths = [Path('.github/dependabot.yml'), *Path('.github/workflows').glob('*.yml')]
for path in paths:
    yaml.safe_load(path.read_text())
    print(f'OK {path}')
PY

Expected all files print OK.

  • Step 2: Check workflows recognized by GitHub CLI

Run:

gh workflow list

Expected output includes:

Build and Push Docker Images
Deploy Docker to VPS
  • Step 3: Inspect final diff

Run:

git diff -- .github/workflows .github/dependabot.yml

Expected:

  • docker-build-push.yml has dispatch validation, wait-submodule-ref, job permissions, and manifest push retry.

  • deploy-docker.yml has non-recursive checkout and read-only permissions.

  • dependabot.yml has github-actions updates.

  • Step 4: Commit any final validation fixes

If Step 1 or Step 2 required fixes, commit them:

git add .github/workflows .github/dependabot.yml
git commit -m "ci: finalize production workflow hardening"

If no fixes were needed, do not create an empty commit.