Vol. 1 · No. 001   Home network edition

Today I Learned

All the bugs that are fit to print

How-to · Home network · AirPrint

AirPrint for a Printer That Never Had It

My Epson L3250 doesn’t support AirPrint, so my iPhone can’t see it. One small container on my Synology NAS fixes that. Here’s how it works, and how to set it up for your own printer.

TL;DR

Run CUPS and Avahi in a Docker container on something that’s always on (a NAS, a Raspberry Pi), add your printer to CUPS with its proper driver, and let the container advertise it as an AirPrint printer. The container accepts what iOS sends (PDF or Apple’s URF raster), converts it with the driver, and passes it on. Use host networking, pin the image version, and don’t let the container publish the host’s name.

Related to

  • AirPrint
  • CUPS
  • Bonjour / mDNS
  • Docker
  • Synology NAS
  • Home lab

What you’ll learn

  1. What an iPhone checks before it lists a printer as AirPrint.
  2. How a CUPS and Avahi relay adds AirPrint to a printer that doesn’t have it.
  3. A working Docker setup on a Synology NAS, step by step.
  4. How to check it from a Mac before you trust your phone.

Who it’s for

Anyone with a network printer their iPhone or iPad can’t see, and a NAS, Raspberry Pi or home server that stays on.

Level: beginner to intermediate. You’ll edit a compose file and use a terminal.

My printer is an Epson L3250, an ink-tank printer on my Wi-Fi. It prints well and it’s cheap to run. What it doesn’t have is AirPrint, so when I tap Share → Print on my iPhone, it simply isn’t in the list.

I didn’t want a new printer. My Synology NAS is always on and sits on the same network, so it runs a small container that speaks AirPrint to my phone and the printer’s own language to the printer. This issue explains how that works, then walks through the setup so you can do the same with yours.

It’s also part one of a pair. When this setup broke months later, finding out why was a story of its own: that’s No. 002.

Background

What AirPrint actually checks

AirPrint isn’t a chip inside the printer. It’s a set of promises in the printer’s Bonjour advert, the small announcement every network printer makes so phones and laptops can find it (No. 002 explains how Bonjour works in detail). Before your iPhone lists a printer in its print menu, it checks four things in that advert.

The printer has to be listed as an IPP printer (_ipp._tcp), carry the _universal subtype, include a URF key saying it understands Apple’s raster format, and accept a document format iOS can send, which means PDF or image/urf. My Epson passes the first two and fails the last two.

Check the iPhone makesEpson’s own advertNAS advert
Listed under _ipp._tcpyesyes
_universal subtypeyesyes
URF key in TXTmissingURF=none
Accepts a format iOS sendspwg-raster, escprpdf, image/urf
ResultNot shown as AirPrintAirPrint printer
Graphic · What the iPhone checks
Before iPhone looking for AirPrint Epson L3250’s own advert ✓ listed under _ipp._tcp ✓ _universal subtype ✗ no URF key ✗ only PWG raster, ESC/P-R not in the print menu After iPhone looking for AirPrint NAS relay’s advert for the Epson ✓ listed under _ipp._tcp ✓ _universal subtype ✓ URF=none ✓ accepts PDF and image/urf AirPrint printer: jobs go to the NAS
Same printer, two adverts. The iPhone doesn’t care what’s inside the box, only what the Bonjour advert promises. The relay’s advert makes the two promises the Epson can’t: a URF key and formats iOS can send.

So the fix isn’t to change the printer. It’s to put something in front of it that makes all four promises honestly, and then keeps them by converting each job into something the printer understands.

How it works

Inside the relay

The relay is one Docker container running two programs you may already know from Linux: CUPS, the standard print server, and Avahi, the Linux version of Bonjour. A small script in the image watches CUPS, and whenever you add a shared printer, it writes an AirPrint advert for it, with the URF key and the formats iOS wants. Avahi announces that advert on the network.

When you print, your iPhone sends the page to CUPS on port 631 as PDF or URF. CUPS runs it through the printer’s driver, here Epson’s ESC/P-R driver, and sends the result to the printer’s raw port, 9100. The printer never sees anything but its own language.

Graphic · Inside the relay
① Advertising the printer (once, when the queue is added) CUPS queue EPSON_L3250, shared printer-update.sh sees the new queue AirPrint advert /services/*.service Avahi multicasts it writes loads the advert reaches the phone over mDNS ② Printing (every job) iPhone Share → Print CUPS on :631 accepts PDF, URF epson-escpr PDF → ESC/P-R Epson L3250 .7 · port 9100 IPP filter raw everything in the middle runs in one container, on the NAS’s own network (host networking)
Two jobs, one container. The top path runs once: adding the queue makes the container write an AirPrint advert for it, and Avahi announces it. The bottom path runs for every page: CUPS takes what the iPhone sends and hands the printer only its own language.
Method

Set it up in five steps

  1. Give the printer a fixed address

    In your router, add a DHCP reservation for the printer so its IP never changes. CUPS will send every job to that address, and if it moves, printing quietly stops. (That’s exactly how No. 002 begins.)

  2. Build an image with your printer’s driver

    Start from the AirPrint image, pinned to a version you’ve tested, and add the driver package. The last step stops the container’s Avahi announcing the host’s own name, which avoids a fight with the NAS’s Bonjour server. Save this as Dockerfile in a project folder:

    FROM chuckcharlie/cups-avahi-airprint:1.2.0
    
    # The printer's driver. Swap in the package for your printer.
    RUN apk add --no-cache epson-inkjet-printer-escpr
    
    # Let the NAS own its name: the container's Avahi announces
    # only the printer, never the host (the story is in No. 002).
    RUN sed -i -E \
          -e "s/^#?publish-addresses=.*/publish-addresses=no/" \
          -e "s/^#?publish-hinfo=.*/publish-hinfo=no/" \
          -e "s/^#?publish-workstation=.*/publish-workstation=no/" \
          /etc/avahi/avahi-daemon.conf
  3. Run it with host networking

    Bonjour announcements only work on the real network, so the container shares the host’s. The two folders keep the CUPS config and the adverts across rebuilds. Save this next to the Dockerfile as compose.yaml:

    services:
      airprint:
        build: .
        image: airprint-relay:1.2.0
        container_name: airprint
        network_mode: host      # Bonjour needs it
        volumes:
          - ./services:/services  # AirPrint adverts
          - ./config:/config      # CUPS settings
        environment:
          - TZ=Etc/UTC            # your timezone
          - CUPSADMIN=admin
          - CUPSPASSWORD=pick-a-real-password
        restart: unless-stopped

    On a Synology NAS, create a project in Container Manager pointing at that folder and choose Build. Anywhere else, run docker compose up -d --build. After you change the Dockerfile, build again: a plain restart keeps running the old image.

  4. Add the printer to CUPS

    Open http://<nas-ip>:631 and sign in with the admin account from the compose file. Under Administration → Add Printer, pick AppSocket/HP JetDirect, enter socket://<printer-ip>:9100, choose your printer’s driver, tick Share this printer, and set the default paper size. Or do it in one command:

    Add the printer, shared, retrying on errors, A4 by defaultNAS over SSH
    sudo docker exec airprint lpadmin -p EPSON_L3250 -E \
      -v socket://<printer-ip>:9100 \
      -m lsb/usr/epson-inkjet-printer-escpr/Epson-L3250_Series-epson-escpr-en.ppd \
      -o printer-is-shared=true -o printer-error-policy=retry-job -o PageSize=A4

    Your model name will differ; lpinfo -m | grep -i l3250 inside the container lists the right one. Setting retry-job means one failed job doesn’t switch the printer off for good.

  5. Check it from a Mac, then print

    Before you reach for your phone, ask the network what it sees. The relay’s advert should show up with URF=none and image/urf in its formats, and the host it names should resolve to your NAS:

    List AirPrint-capable printersMac Terminal
    dns-sd -B _ipp._tcp
    See the relay’s advert: look for URF and pdlMac Terminal
    dns-sd -L "AirPrint EPSON_L3250 @ nas" _ipp._tcp
    Check the host in the advert resolvesMac Terminal
    dns-sd -G v4 nas.local

    Then open a photo on your iPhone, tap Share → Print, and the printer should be there.

If your soufflé collapses

When it doesn’t work

  1. The printer isn’t in the list

    Run dns-sd -B _ipp._tcp on a Mac. No advert means Avahi isn’t announcing it: check the printer is shared in CUPS and that the container uses host networking. Bonjour also doesn’t cross subnets or VLANs, so a phone on a guest network won’t see it.

  2. It’s listed but jobs never arrive

    The host named in the advert has to resolve. If dns-sd -G v4 returns nothing, or your NAS’s name has grown a number like -2, two Bonjour servers are fighting over it. No. 002 is the full story, and the three Avahi lines in step 2 are the fix.

  3. Pages of gibberish

    The raw port prints exactly the bytes it’s given. Gibberish means the queue isn’t using your printer’s own driver.

  4. “Backend failed”

    One of CUPS’s helpers crashed. Its error_log says which one; read it before blaming the printer.

  5. You changed the Dockerfile and nothing changed

    You restarted instead of rebuilding. Build the image again, and give it a new tag if your tool keeps reusing the old one.

Key takeaways
  1. AirPrint is a promise, not a chip. Four things in a Bonjour advert, plus formats iOS can send. Anything that keeps those promises is an AirPrint printer.
  2. Put the driver where CUPS runs. The printer should only ever see its own language.
  3. The boring parts keep it working. Pin the image, give the printer a fixed address, and set the queue to retry.
Next issue

No. 002: The Case of the Printer Nobody Could Reach. What happened when this setup broke, and why my NAS ended up renaming itself 319 times.

Glossary

Words from this issue

AirPrint
Apple’s way of printing from iPhones, iPads and Macs without installing drivers.
Bonjour advert
The records a device announces on the local network so others can find it: what it is, where it is, and what it can do.
CUPS
The standard print server on Linux and macOS. It queues jobs and runs them through drivers.
Avahi
The Linux implementation of Bonjour (mDNS and DNS-SD).
URF
Apple’s raster format for AirPrint, and the advert key that says a printer accepts it.
IPP
Internet Printing Protocol. How the iPhone sends the job to CUPS, on port 631.
Raw port 9100
Also called AppSocket or JetDirect. It prints exactly the bytes it receives.
ESC/P-R
Epson’s native printer language for its inkjets.
Host networking
A Docker mode where the container shares the host’s network, which Bonjour needs.