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.ymlhas dispatch validation,wait-submodule-ref, job permissions, and manifest push retry. -
deploy-docker.ymlhas non-recursive checkout and read-only permissions. -
dependabot.ymlhasgithub-actionsupdates. -
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.