Alle Artikel

Bilder in GitHub Actions komprimieren

PiPic-Team
Ratgeber
npx @pipic/cli ./assets --replace --json
✓ compressed · committed back to the PR

Jemand legt ein 4 MB großes Hero-Bild in assets/ ab, niemand bemerkt es, und zwei Wochen später fällt der Lighthouse-Score. Die Lösung ist kein weiterer Punkt auf der Code-Review-Checkliste, sondern ein Schritt in der Pipeline, der das Bild verkleinert, bevor es ausgeliefert wird.

Das ist der ganze Workflow:

# .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

Der paths-Filter leistet dort echte Arbeit. Ohne ihn läuft der Job bei jedem Pull Request, und jeder Lauf geht das gesamte assets/-Verzeichnis durch — warum das zählt, kommt gleich.

Der Token

CI hat keinen Browser, pipic login fällt also aus. Erstelle einen Token auf deiner Kontoseite, speichere ihn als Repository-Secret namens PIPIC_TOKEN, und die CLI holt ihn sich aus der Umgebung — er hat Vorrang vor der Anmeldedatei, es gibt also nichts, wo man sich einloggen müsste.

Beachte, dass pipic token create dich bewusst auf die Webseite schickt, statt einen Token im Terminal auszustellen. Eine gestohlene CI-Anmeldeinformation sollte keine neuen ausstellen können.

Das Flag, das du nicht weglassen kannst

--replace überschreibt an Ort und Stelle, -o <dir> schreibt woanders hin. Beide schließen sich gegenseitig aus, und eines von beiden ist Pflicht: npx @pipic/cli ./assets ohne beide bricht mit einem Nutzungsfehler ab, bevor auch nur eine Anfrage rausgeht.

In einem Workflow, der die Ergebnisse zurückcommittet, willst du --replace: die Dateien im Arbeitsverzeichnis sind genau die, die du gleich committest.

Das Ergebnis lesen

--json gibt pro Datei ein NDJSON-Objekt aus. In einem Workflow musst du es meist nicht parsen — git diff --quiet sagt dir bereits, ob sich etwas geändert hat — aber es landet im Job-Log, und dort schaust du nach, wenn ein Lauf dich überrascht:

{"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 ist kein Fehler. Dateien werden nur zurückgeschrieben, wenn das Ergebnis tatsächlich kleiner ist — ein bereits optimiertes Bild meldet also skipped mit Begründung, und git diff bleibt dafür sauber.

Worauf man wirklich aufbauen sollte, sind die Exit-Codes:

CodeBedeutungIm Workflow
0Alles erfolgreichSchritt läuft durch
1Einige Dateien fehlgeschlagenSchritt schlägt fehl — im Log nachsehen, welche
2NutzungsfehlerDein Befehl ist falsch; Workflow korrigieren
3Anmeldung erforderlichPIPIC_TOKEN fehlt, ist falsch oder widerrufen
4Monatskontingent aufgebrauchtSiehe unten

Eine 3 in CI heißt fast immer, dass das Secret den Schritt nicht erreicht hat — ein Tippfehler im env:-Block oder ein Secret, das für Pull Requests aus Forks nicht verfügbar ist.

Die Kontingentrechnung

Das ist der Teil, über den Leute stolpern, deshalb klar heraus: das monatliche Kontingent zählt Anfragen, nicht geänderte Dateien. Jedes Bild, das die CLI an den Server übergibt, kostet eine — auch die, die als „schon klein genug" zurückkommen.

Es gibt kein lokales Gedächtnis darüber, was vorher komprimiert wurde. Zeig auf ein Verzeichnis, und es verarbeitet bei jedem Lauf alles darin.

Ein Job, der bei jedem Pull Request einen assets/-Ordner mit 200 Bildern durchgeht, verbraucht also 200 Einheiten pro Lauf — und der kostenlose Tarif hat 100 im Monat. Zwei praktische Konsequenzen:

  • Halte den paths-Filter eng. Er verhindert, dass der Job läuft, wenn sich keine Bilder geändert haben — und das ist meistens der Fall.
  • Richte den Befehl auf das engste mögliche Verzeichnis. ./assets/heroes schlägt ./assets, wenn dort die Bewegung stattfindet.

Wenn der Job nur die in diesem Pull Request geänderten Bilder anfassen soll, übergib sie explizit, statt ein Verzeichnis zu reichen:

      - 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

Damit wird aus festen Kosten pro Lauf ein Kostenanteil proportional zum Diff — in den meisten Repositories eine Handvoll Dateien.

Exit 4 heißt, das Kontingent für diesen Monat ist aufgebraucht. Es wird nichts automatisch berechnet: Pro hebt das Kontingent auf 5.000, und das Komprimieren auf pipic.cc bleibt in jedem Fall kostenlos und unbegrenzt.

Was dieser Workflow nicht tut

Er blockiert den Pull Request nicht. Es gibt keinen Modus „nur prüfen, bei unoptimierten Bildern fehlschlagen" — die CLI komprimiert, und du entscheidest, was mit dem Ergebnis passiert. Wenn du eine harte Schranke willst, sieht sie so aus: Komprimierung ausführen, den Schritt fehlschlagen lassen, wenn git diff nicht leer ist, und die optimierten Dateien vom Autor selbst committen lassen.

      - 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
          }

Das tauscht Bequemlichkeit gegen Kontrolle: nichts wird im Namen des Autors committet, aber irgendjemand muss es erledigen.

Probier es mit deinen eigenen Bildern
Kostenlos, ohne Anmeldung, nie gespeichert.
Bilder komprimieren