記事一覧に戻る

GitHub Actions で画像を圧縮する

PiPic チーム
ガイド
npx @pipic/cli ./assets --replace --json
✓ compressed · committed back to the PR

誰かが assets/ に 4 MB のヒーロー画像を置き、誰も気づかないまま二週間後に Lighthouse のスコアが落ちる。直し方はコードレビューのチェックリストを増やすことでは なく、公開される前に画像を小さくするステップをパイプラインに入れることだ。

これが全体像:

# .github/workflows/optimize-images.yml
name: Optimize images

on:
  pull_request:
    paths:
      - 'assets/**.png'
      - 'assets/**.jpg'
      - 'assets/**.jpeg'
      - 'assets/**.webp'
      - 'assets/**.avif'

permissions:
  contents: write

jobs:
  compress:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Compress
        env:
          PIPIC_TOKEN: ${{ secrets.PIPIC_TOKEN }}
        run: npx @pipic/cli ./assets --replace --json

      - name: Commit if anything shrank
        run: |
          if git diff --quiet; then
            echo "Nothing to commit — images were already optimal."
            exit 0
          fi
          git config user.name  "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git commit -am "chore: compress images"
          git push

ここで paths フィルターは実際に仕事をしている。これがないとジョブはすべての プルリクエストで走り、毎回 assets/ ディレクトリ全体を歩くことになる——それがなぜ 問題なのかは後述する。

トークン

CI にブラウザはないので pipic login は選択肢に入らない。 アカウントページでトークンを作り、PIPIC_TOKEN という名前 のリポジトリシークレットとして保存すれば、CLI が環境変数から拾ってくれる——認証情報 ファイルより優先されるので、ログインという手順自体が存在しない。

pipic token create があえてターミナルで発行せずウェブページへ誘導するのは意図的だ。 盗まれた CI 認証情報が、新しい認証情報を発行できてはならない。

省略できないフラグ

--replace はその場で上書きし、-o <dir> は別の場所へ書き出す。両者は排他で、 どちらか一方が必須だ——npx @pipic/cli ./assets をどちらもなしで実行すると、 リクエストを一つも送る前に使い方エラーで終了する。

結果をコミットし返すワークフローで欲しいのは --replace のほうだ。作業ツリーの ファイルが、そのままこれからコミットするファイルになる。

結果を読む

--json はファイルごとに NDJSON オブジェクトを 1 行ずつ出力する。ワークフローの中では たいていパースする必要はない——変化があったかどうかは git diff --quiet が既に教えて くれる——が、ジョブログには残る。想定外の結果が出たときに見るのはそこだ:

{"file":"assets/hero.png","status":"ok","before":2411233,"after":486201,"saved":1925032}
{"file":"assets/logo.webp","status":"skipped","before":18944,"after":18944,"saved":0,"error":"not smaller — original kept"}

skipped は失敗ではない。結果が実際に小さくなったときだけ書き戻すので、すでに最適化 済みの画像は理由付きで skipped を報告する——そして git diff はそのファイルについて きれいなままだ。

作り込むべきは終了コードのほうだ:

コード意味ワークフローでは
0すべて成功ステップは通る
1一部のファイルが失敗ステップは失敗——どれかはログで確認
2使い方エラーコマンドが誤り。ワークフローを直す
3サインインが必要PIPIC_TOKEN が無い・誤り・失効
4月間クォータ超過下記参照

CI での 3 はほぼ必ず、シークレットがそのステップに届いていないという意味だ—— env: ブロックのタイプミスか、fork からのプルリクエストで参照できないシークレットか。

クォータの計算

ここでつまずく人が多いので率直に書く:月間の割り当ては、変化したファイル数ではなく リクエスト数を数える。 CLI がサーバーに渡した画像は、「もう十分小さい」と返ってきた ものも含めて 1 回ずつ消費する。

以前に何を圧縮したかというローカルの記憶はない。ディレクトリを指定すれば、そのディレ クトリの中身を毎回すべて処理する。

つまり 200 枚の assets/ フォルダをすべてのプルリクエストで歩くジョブは、1 回の実行で 月間割り当てを 200 消費する——無料枠は月 100 だ。実務的な結論は二つ:

  • paths フィルターは絞る。 画像に変更がないときにジョブが走らなくなる。そして それが大半の場合だ。
  • コマンドは可能な限り狭いディレクトリに向ける。 変更が集中しているのが ./assets/heroes なら、./assets よりそちらを指す。

そのプルリクエストで変更された画像だけを対象にしたいなら、ディレクトリを渡すのでは なく明示的にファイルを渡す:

      - name: Compress changed images
        env:
          PIPIC_TOKEN: ${{ secrets.PIPIC_TOKEN }}
        run: |
          FILES=$(git diff --name-only --diff-filter=d origin/${{ github.base_ref }}... \
                  -- 'assets/**.png' 'assets/**.jpg' 'assets/**.jpeg' 'assets/**.webp' 'assets/**.avif')
          if [ -z "$FILES" ]; then
            echo "No image changes."
            exit 0
          fi
          echo "$FILES" | xargs npx @pipic/cli --replace --json

これで実行ごとの固定コストが、差分に比例するコストに変わる。ほとんどのリポジトリでは 数ファイル分でしかない。

終了コード 4 はその月の割り当てを使い切ったという意味だ。自動的な課金は一切ない—— Pro は割り当てを 5,000 に引き上げるが、pipic.cc 上での 圧縮はどちらにしても無料・無制限のままだ。

このワークフローがしないこと

プルリクエストをブロックはしない。「チェックだけして未最適化なら失敗」というモードは ない——CLI は圧縮するだけで、その結果をどう扱うかはあなたが決める。厳格なゲートが 欲しいなら形はこうなる:圧縮を実行し、git diff が空でなければステップを失敗させ、 最適化済みのファイルは作者自身にコミットさせる。

      - name: Fail if images weren't optimised
        run: |
          git diff --quiet || {
            echo "::error::These images can be compressed further. Run pipic locally and commit."
            git diff --stat
            exit 1
          }

これは利便性と引き換えに制御を得る取引だ。作者の名前で何かがコミットされることはない が、代わりに誰かがその作業をしに行く必要がある。

自分の画像で試してみる
無料、登録不要、保存も一切なし。
画像を圧縮する