Of course we (only) do AirPrint!

After approximately one month, the Apple journey is going well. Even picking up speed one could say, as I’m beginning to feel comfortable, or even at home a lot of the time. Still not closer to appreciating how the application switcher works, though ;-)

As I’ve done a full ecosystem switch, including mobile phone and tablet, also for these devices there is a need to interoperate with things like printers that exist in our household. This writeup is about how that worked out for me.

CUPS is an Apple project, so how hard can it be?

Although I did not realize this before, the venerable CUPS printing system is an Apple-driven project. That is good. Especially because Apple products (iOS devices, specifically) don’t do anything with IPP, nor could I find a manufacturer ‘driver app’ to connect with our little oldish Samsung color laser printer. Instead, Apple products are looking for AirPrint devices…

As I have a Raspberry Pi running our home automation, DNS filter and a bunch of other around-the-house services, there should be something we can do with that. Open source to the rescue: of course there is! A combination of CUPS and Avahi will act as a relay, making CUPS-driven printers available in the local network via Avahi service discovery.

A first quick trial, installing CUPS and Avahi directly in the Pi’s Ubuntu host system, proves that this works quite painlessly. But all other workloads on my Pi are containerized and designed to make the overall setup as reproducible and modular as possible…

Is there anything out there?

Yes and no. What we want is a ready-made docker image that acts as our AirPrint relay, with minimal configuration and fuss needed. There are some Docker recipes, many of which seem to be descendants of quadportnicks docker-cups-avahi project, with associated images on dockerhub. I couldn’t find one that worked for me though - I was looking for a combination of arm64 support, included drivers for my Samsung printer, and maybe not being abandoned years ago. So after looking around a bit, I decided to hit that fork button and adapt something for my needs.

What do we want?

My AirPrint relay image should have the following properties:

  • Include drivers for my printer, of course. Maybe even be a generic thing that comes with all available drivers, to become more widely useful?
  • Allow persistent storage of printer-related configuration outside of the container image, while stuff like cups configuration should ‘just work’ and stay out of the way otherwise.
  • Size is a concern too, within limits - generic usefulness means we won’t optimize for size, but no need to be wasteful either.
  • Support for 64bit arm architectures. Maybe even go multi-arch?

The aforementioned cups-avahi-airprint project does cover a lot of these bases already, so let’s fork that. Actually, I forked a fork of that project, for no good reason other than that I’ve been looking at it at the time.

Building an AirPrint Relay

There was actually a reason for basing off chuckcharlies version of cups-avahi-airprint: I liked the idea of using Alpine as base image. However, after spending too much time trying to find out whether my old Samsung CLP-365 was supported by any package on that distro and not finding anything useful, I reverted the build back to Ubuntu (my printer is supported by ubuntu package printer-driver-foo2zjs). Ubuntu does provide a quite minimal base image for docker stuff, and while it is not as small as Alpine, I was willing to take the size hit to just get my printer supported and move on with things.

The switch to Ubuntu brought one problem: during build, the systems wants manual input for configuring the tzdata package. Luckily the internet has a solution for that (see the Dockerfile).

Next step: add all printer drivers to the image build that I could find. I’ll thank myself in the future should I ever switch hardware, plus maybe this way the image becomes useful for other people too.

That’s most of the visible changes already. I spent a lot of time learning how to get this image to correctly register with my Traefik router, so that the CUPS dashboard becomes reachable via a nice name instead of IP and port. The problem was that CUPS refuses requests that come with a wrong host entry in the header (wrong as far as CUPS is concerned). The fix for that is a combination of cupsd.conf and Traefik settings:

cupsd.conf should contain the following lines

Port *:631      # this replaces the default 'Listen ...' directive
ServerAlias *

This Traefik label is needed in the AirPrint Relay docker-compose.yml:

    - "traefik.http.services.airprint-relay.loadbalancer.passhostheader=false"

The latter took me quite some digging to figure out - but with these, Traefik routing works for me.

The original project came with some scripting- and Python magic for handling CUPS startup and Avahi service file creation from CUPS-configured printers, which I didn’t change much beyond minor naming convention alignments and adaptions around current Ubuntu versions of adduser and addgroup. Oh, and I fixed one of the Python-generated Avahi service fields to provide printer location (as it’s used by MacOS), instead of printer info.

The final steps were brushing up the project README, and adding a multi-arch build action following the regular docker documentation.

The Result

The result of all of this is available for easy consumption from dockerhub - a one-click solution for deploying an AirPrint relay service into your home network.