All the bugs that are fit to print
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.
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.
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.
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 makes | Epson’s own advert | NAS advert |
|---|---|---|
| Listed under _ipp._tcp | yes | yes |
| _universal subtype | yes | yes |
| URF key in TXT | missing | URF=none |
| Accepts a format iOS sends | pwg-raster, escpr | pdf, image/urf |
| Result | Not shown as AirPrint | AirPrint printer |
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.
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.
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.)
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.confBonjour 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-stoppedOn 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.
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:
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.
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:
dns-sd -B _ipp._tcp
dns-sd -L "AirPrint EPSON_L3250 @ nas" _ipp._tcp
dns-sd -G v4 nas.local
Then open a photo on your iPhone, tap Share → Print, and the printer should be there.
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.
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.
The raw port prints exactly the bytes it’s given. Gibberish means the queue isn’t using your printer’s own driver.
One of CUPS’s helpers crashed. Its error_log says which one; read it before blaming the printer.
You restarted instead of rebuilding. Build the image again, and give it a new tag if your tool keeps reusing the old one.