Todos los artículos

Comprimir imágenes en GitHub Actions

Equipo de PiPic
Guías
npx @pipic/cli ./assets --replace --json
✓ compressed · committed back to the PR

Alguien deja una imagen de portada de 4 MB en assets/ y nadie se da cuenta hasta que la puntuación de Lighthouse cae dos semanas después. La solución no es otro punto en la lista de revisión de código: es un paso en el pipeline que encoge la imagen antes de que llegue a producción.

Esto es todo:

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

El filtro paths está haciendo un trabajo real ahí. Sin él, el job se ejecuta en cada pull request, y cada ejecución recorre todo el directorio assets/ — lo cual importa por razones que veremos enseguida.

El token

CI no tiene navegador, así que pipic login no es una opción. Crea un token en tu página de cuenta, guárdalo como un secreto del repositorio llamado PIPIC_TOKEN, y la CLI lo tomará del entorno: tiene prioridad sobre el archivo de credenciales, así que no hay nada en lo que iniciar sesión.

Fíjate en que pipic token create te envía deliberadamente a la página web en vez de emitir uno desde la terminal. Una credencial de CI robada no debería poder emitir credenciales nuevas.

La bandera que no puedes omitir

--replace sobrescribe en el sitio; -o <dir> escribe en otro lugar. Son mutuamente excluyentes y una de las dos es obligatoria: npx @pipic/cli ./assets sin ninguna termina con un error de uso antes de enviar una sola petición.

En un workflow que devuelve los resultados con un commit, lo que quieres es --replace: los archivos del árbol de trabajo son exactamente los que vas a commitear.

Leer el resultado

--json imprime un objeto NDJSON por archivo. Dentro de un workflow casi nunca necesitas parsearlo — git diff --quiet ya te dice si algo cambió — pero queda en el log del job, que es donde vas a mirar cuando una ejecución te sorprenda:

{"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 no es un fallo. Los archivos solo se reescriben cuando el resultado es realmente más pequeño, así que una imagen ya optimizada reporta skipped con el motivo — y git diff se mantiene limpio para ella.

Los códigos de salida son la parte sobre la que construir:

CódigoSignificadoEn un workflow
0Todo correctoEl paso pasa
1Algunos archivos fallaronEl paso falla — mira el log para ver cuáles
2Error de usoTu comando está mal; corrige el workflow
3Hace falta iniciar sesiónFalta PIPIC_TOKEN, es incorrecto o fue revocado
4Cuota mensual agotadaVer más abajo

Un 3 en CI casi siempre significa que el secreto no llegó al paso: una errata en el bloque env:, o un secreto que no está disponible para pull requests desde forks.

Las cuentas de la cuota

Esta es la parte que pilla a la gente, así que aquí va sin rodeos: la asignación mensual cuenta peticiones, no archivos modificados. Cada imagen que la CLI entrega al servidor cuesta una, incluidas las que vuelven como «ya era suficientemente pequeña».

No hay memoria local de lo que se comprimió antes. Apúntala a un directorio y procesará todo lo que haya en él, en cada ejecución.

Así que un job que recorre una carpeta assets/ de 200 imágenes en cada pull request gasta 200 de tu asignación mensual por ejecución — y el plan gratuito son 100 al mes. Dos consecuencias prácticas:

  • Mantén el filtro paths ajustado. Evita que el job se ejecute cuando no ha cambiado ninguna imagen, que es la mayor parte del tiempo.
  • Apunta el comando al directorio más estrecho posible. ./assets/heroes es mejor que ./assets si ahí es donde está el movimiento.

Si quieres que el job toque solo las imágenes modificadas en ese pull request, pásalas explícitamente en lugar de entregar un directorio:

      - 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

Eso convierte un coste fijo por ejecución en un coste proporcional al diff, que en la mayoría de repositorios es un puñado de archivos.

El código 4 significa que te has quedado sin cuota este mes. No se cobra nada automáticamente: Pro sube la asignación a 5.000, y comprimir en pipic.cc sigue siendo gratis e ilimitado en cualquier caso.

Lo que este workflow no hace

No bloquea el pull request. No hay un modo «solo comprobar, fallar si no está optimizado»: la CLI comprime y tú decides qué hacer con el resultado. Si quieres una barrera dura, la forma es esta: ejecuta la compresión y después haz fallar el paso cuando git diff no esté vacío, dejando que el autor commitee él mismo los archivos optimizados.

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

Eso cambia comodidad por control: nada se commitea en nombre del autor, pero alguien tiene que ir a hacerlo.

Pruébalo con tus propias imágenes
Gratis, sin registro, nunca almacenadas.
Comprimir imágenes