Bài viết này hiện chỉ có bằng tiếng Anh.

WebPOct 22, 20257 min read

WebP in GitHub Actions: A Minimal CI Workflow

CI can help keep image optimization repeatable, but it should not silently change visual assets without review. A minimal GitHub Actions workflow should check that conversion commands run, outputs are predictable, and committed assets match the expected state. It should not hide a major image migration inside a build step nobody watches.

The practical goal is simple: use CI to catch mistakes, not to remove human visual review.

A three-line workflow that installs a converter and runs it on every push hides too many review decisions. A CI workflow needs a clear write policy, a report artifact, exit-code handling, Free-plan truncation handling, and a visible place for reviewers to inspect what happened.

Decide What CI Should Enforce#

Before writing YAML, choose the rule. Common rules include:

  • run a dry run and report eligible files
  • verify that generated WebP files exist
  • fail when source images were added without optimized outputs
  • produce a JSON or NDJSON conversion report
  • check that the command works on a clean machine

Avoid starting with "CI should optimize everything on every push." That can create noisy diffs, slow builds, and unreviewed visual changes.

For a first workflow, prefer this rule:

CI converts images into a temporary output folder, saves the NDJSON report,
fails on mechanical conversion problems, and uploads outputs for review.
CI does not commit generated images back to the pull request.

If your repository requires optimized files to be committed, add that as a separate check after the temporary conversion workflow is stable.

Use a Minimal Reviewable Workflow#

A minimal workflow can install the CLI, optionally activate a license from a secret, convert into a CI-only folder, parse the NDJSON report, and upload artifacts. Save this as .github/workflows/webp-check.yml and adjust the image paths:

name: webp-check

on:
  pull_request:
    paths:
      - "public/images/**"

jobs:
  webp-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install GetWebP CLI
        run: |
          curl -fsSL -o getwebp https://getwebp.com/download/getwebp-linux-x64
          chmod +x getwebp
          sudo mv getwebp /usr/local/bin/getwebp
          getwebp --version

      - name: Prepare report folders
        run: mkdir -p .getwebp-ci/reports .getwebp-ci/output

      - name: Activate license when secret is available
        env:
          GETWEBP_LICENSE_KEY: ${{ secrets.GETWEBP_LICENSE_KEY }}
        run: |
          if [ -n "${GETWEBP_LICENSE_KEY:-}" ]; then
            getwebp auth "$GETWEBP_LICENSE_KEY" --json > .getwebp-ci/reports/auth.ndjson
          else
            echo "GETWEBP_LICENSE_KEY is not set; running with Free plan limits."
          fi

      - name: Convert images into CI output folder
        run: |
          set +e
          getwebp ./public/images \
            -o ./.getwebp-ci/output \
            --recursive \
            --quality 82 \
            --json > ./.getwebp-ci/reports/conversion.ndjson 2> ./.getwebp-ci/reports/conversion.stderr.log
          code=$?
          set -e

          echo "$code" > ./.getwebp-ci/reports/exit-code.txt

          if jq -e 'select(.type == "convert.truncated")' ./.getwebp-ci/reports/conversion.ndjson >/dev/null; then
            echo "::error::GetWebP stopped before processing every file. Check convert.truncated in conversion.ndjson."
            exit 6
          fi

          if [ "$code" -ne 0 ]; then
            echo "::error::GetWebP exited with code $code."
            jq -r '
              select(.type == "convert.completed")
              | .data.results[]
              | select(.status == "error")
              | "::error file=\(.file)::\(.error)"
            ' ./.getwebp-ci/reports/conversion.ndjson || true
            exit "$code"
          fi

          jq -e '
            select(.type == "convert.completed")
            | .data.failedCount == 0
          ' ./.getwebp-ci/reports/conversion.ndjson >/dev/null

      - name: Write summary
        if: always()
        run: |
          echo "### WebP check" >> "$GITHUB_STEP_SUMMARY"
          echo "" >> "$GITHUB_STEP_SUMMARY"
          if grep -q '"convert.completed"' ./.getwebp-ci/reports/conversion.ndjson 2>/dev/null; then
            completed=$(grep '"convert.completed"' ./.getwebp-ci/reports/conversion.ndjson | tail -n 1)
            echo "- Total: $(printf '%s' "$completed" | jq -r '.data.total')" >> "$GITHUB_STEP_SUMMARY"
            echo "- Succeeded: $(printf '%s' "$completed" | jq -r '.data.successCount')" >> "$GITHUB_STEP_SUMMARY"
            echo "- Failed: $(printf '%s' "$completed" | jq -r '.data.failedCount')" >> "$GITHUB_STEP_SUMMARY"
          else
            echo "- No convert.completed event found." >> "$GITHUB_STEP_SUMMARY"
          fi

      - name: Upload image check artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: webp-check
          path: |
            .getwebp-ci/reports/**
            .getwebp-ci/output/**

This version writes generated files only under .getwebp-ci/, which is a CI artifact folder. It does not modify the pull request. Reviewers can inspect the output folder when the job fails or when a conversion rule changes.

In production, use your organization's approved install path. The binary download above is readable for a minimal example; stricter environments should pin a version and verify a checksum before moving the binary onto PATH.

Use JSON Output When Machines Need to Read It#

If the next step needs structured data, use --json:

getwebp ./public/images --json

GetWebP emits newline-delimited JSON on stdout. The JSON output reference documents the version, convert.completed, convert.truncated, and convert.failed event shapes. Human warnings go to stderr, so the workflow above stores them in conversion.stderr.log instead of mixing them into the report.

Structured output is easier for CI scripts and AI agents to parse than human-formatted console text. It also lets you build rules later, such as reporting conversion counts, detecting failed files, listing output paths, or grouping unsupported inputs.

Keep the first rule narrow. A small, reliable check is more useful than a complex workflow that the team ignores because it fails for unclear reasons.

Handle Free and Pro Differences Explicitly#

The Free plan can run conversion without a license, but it is limited to 20 files per run, serial processing, and a 3-second delay between files. When the limit is hit, the NDJSON stream reports convert.truncated. A workflow must fail or deliberately split the job; it should not treat the processed subset as a full pass.

If the repository needs Pro behavior, store the key as a GitHub Actions secret named GETWEBP_LICENSE_KEY. Do not reference the secret directly in shell output. The workflow above passes it through the step environment and only runs getwebp auth when the value is present.

Activation, status checks, and logout can use --json, but keep those reports separate from conversion.ndjson so parser logic stays simple.

Do Not Skip Visual Review#

CI cannot decide whether a product texture looks trustworthy, a screenshot remains readable, or a transparent edge looks clean on the page background. It can only check mechanical facts.

Use CI for:

  • command availability
  • file existence
  • parseable reports
  • unexpected failures
  • repeatability on a clean runner

Use human review for:

  • visual quality
  • brand color
  • screenshot readability
  • product detail
  • acceptable compression tradeoffs

Those are different jobs.

Avoid Commit-Time Surprises#

If CI generates files, decide whether those files should be committed, uploaded as artifacts, or rejected as a missing-output failure. Do not let CI create optimized images that never enter review.

For most teams, a safer pattern is:

  1. developer or content owner generates outputs locally
  2. outputs are committed with the source change
  3. CI verifies that conversion still works on a clean runner
  4. reviewers inspect the visual result

This keeps the image change visible in the pull request.

Avoid a workflow that commits back to the same branch by default. It can create bot-authored changes that reviewers did not inspect, and it can interact poorly with protected branches or forked pull requests.

Add Missing-Output Checks Later#

Temporary conversion proves that the tool can process the current images. It does not prove that the committed optimized folder is complete. If your repository uses separate source and optimized folders, add a second check:

public/images/originals/
public/images/optimized/

Then compare expected .webp paths against files in the optimized folder. Keep that check scoped to changed files or a small folder at first. A repository-wide missing-output gate can fail because of old assets unrelated to the pull request.

Protect Secrets and Licenses#

If your CI workflow uses a Pro license or private package registry, store secrets in GitHub Actions secrets and avoid printing them in logs. Keep license activation separate from article examples unless the repository actually needs it.

For public or open-source repositories, prefer checks that can run without sensitive credentials. That makes the workflow easier for contributors and forks.

Also decide how device slots are handled. Ephemeral CI runners can look like new devices. If a project runs many concurrent jobs, prefer a license policy that supports the expected runner pattern, and make auth failures visible in a separate auth.ndjson artifact.

GitHub's Actions documentation explains workflow syntax and runner behavior. Google's WebP documentation provides format background for the images being generated.

Make Failures Actionable#

When the workflow fails, the log should tell the contributor what to do next. A vague image job failure slows review. Prefer messages that identify the folder, command, and expected fix:

Image check failed: new files were added under public/images,
but matching optimized outputs were not committed.
Run the documented GetWebP command and review the results.

For NDJSON-driven workflows, include the exit code and the event type:

GetWebP exited with code 3.
convert.completed reported failedCount=2.
Open webp-check artifact > reports/conversion.ndjson for failed file paths.

Actionable failures keep CI from becoming background noise.

Start With One Folder#

Run the workflow on one stable image folder before expanding it across the project. CI image checks should earn trust gradually. Once the dry run and reporting behavior are reliable, you can add stricter rules for missing outputs or failed conversions.

A minimal CI workflow is not a replacement for design judgment. It is a guardrail that makes image conversion repeatable, visible, and less dependent on one person's local machine.

Jack avatar

Jack

GetWebP Editor

Jack writes GetWebP guides about local-first image conversion, WebP workflows, browser compatibility, and practical performance checks for teams that publish images on the web.