508 words
3 minutes
Optimizing PNGs in GitHub Actions using Oxipng

Optimizing PNGs in GitHub Actions using Oxipng#

My datasette-screenshots repository generates screenshots of Datasette using my shot-scraper tool, for people who need them for articles or similar.

Jacob Weisz suggested optimizing these images as they were quite big. I want them to be as high quality as possible (I even take them using --retina mode), but that didn’t mean I couldn’t use lossless compression on them.

I often use squoosh.app to run a version of Oxipng compiled to WebAssembly in my browser. I decided to figure out how to run that same program in GitHub Actions.

Installing Rust apps using Cargo#

Surprisingly there isn’t yet a packaged version of Oxipng for Ubuntu - so I needed another way of installing it.

The project README suggests installing it using cargo install oxipng.

I used the tmate trick to try that out in a GitHub Actions worker - the cargo command is available by default but it took over a minute to fetch and compile all of the dependencies.

I didn’t want to do this on every run, so I looked into ways to cache the built program. Thankfully the actions/cache action documents how to use it with Rust.

The full recipe for installing Oxipng in GitHub Actions looks like this:

- name: Cache Oxipng
uses: actions/cache@v3
with:
path: ~/.cargo/
key: ${{ runner.os }}-cargo
- name: Install Oxipng
run: |
which oxipng || cargo install oxipng

The first time the action runs it does a full compile of Oxipng - but on subsequent runs the which oxipng command succeeds and skips the cargo install step entirely.

Running Oxipng in an Action#

All of the PNGs that I wanted to optimize were in the root of my checkout, so I added this step:

- name: Optimize PNGs
run: |-
oxipng -o 4 -i 0 --strip safe *.png

The -o 4 is the highest recommended level of optimization.

-i 0 causes it to remove interlacing - “Interlacing can add 25-50% to the size of an optimized image” according to the README.

--strip safe strips out any image metadata that is guaranteed not to affect how the image is rendered.

Oxipng updates the specified images in place, hence the *.png at the end.

Testing this in a branch first#

I tested this all in a branch first so that I could see if it was working correctly.

Since my workflow usually pushes any changed files back to the same GitHub repository, I added a check to that step which caused it to only run on pushes to the main branch:

- name: Commit and push
if: github.ref == 'refs/heads/main'
run: |-
git config user.name "Automated"
...

But I wanted to preview the generated images - so I added this step in the branch to save them to an artifact zip file that I could then inspect:

- name: Artifacts
uses: actions/upload-artifact@v3
with:
name: screenshots
path: "*.png"

Once I got it working, I squash-merged this pull request back into main.

The result#

Oxipng worked really well!

It reduced the size of all three of my screenshots:

This commit shows the difference and lets you compare both images.

Optimizing PNGs in GitHub Actions using Oxipng
https://mranv.pages.dev/posts/optimizing-pngs-in-github-actions-using-oxipng/
Author
Anubhav Gain
Published at
2025-01-10
License
CC BY-NC-SA 4.0