The queue was still advertised over mDNS while cupsd never bound TCP 631: a rootless Podman container on the host network cannot bind the privileged port. Apple clients then discovered a printer they could not reach and reported a confusing _ipps connection failure. * entrypoint.sh: verify that CUPS accepts connections on 631 after startup and exit with an actionable message otherwise * entrypoint.sh: mark the script executable in the repository * cupsd.conf: drop BrowseRemoteProtocols, which CUPS 2.4 no longer supports and which logs an error on every start * README: add a troubleshooting section for the port-binding failure and the _ipps/TLS certificate requirement, and document the /etc/cups bind-mount caveat plus the CUPS 3 PPD deprecation
Printserver
A containerized CUPS print server that shares a Brother HL-4150CDN network printer over IPP and advertises it via AirPrint (mDNS/DNS-SD). It prints by sending PostScript (BR-Script3) straight to the printer, so no vendor driver and no 32-bit libraries run in the container.
The container bundles:
- CUPS (IPP + web admin on port 631) with cups-filters for format conversion
- Avahi / DNS-SD for AirPrint discovery (UDP 5353)
- Brother's official HL-4150CDN BR-Script3 PPD for device options
How it works
The HL-4150CDN emulates PCL6 and BR-Script3 (PostScript 3), and accepts
PostScript over raw AppSocket (port 9100). CUPS converts whatever a client
sends into PostScript and the BR-Script3 PPD turns the selected queue options
into PostScript setpagedevice instructions:
macOS / iOS / Windows / Linux container printer
┌─────────────────┐ IPP/AirPrint ┌──────────────────────────┐ raw ┌────────────┐
│ AirPrint (URF) │ ───────────────▶ │ CUPS │ ──────▶ │ HL-4150CDN │
│ PDF / PostScript│ │ URF/PWG/PDF → PostScript│ :9100 │ BR-Script3 │
└─────────────────┘ │ pstops → setpagedevice │ └────────────┘
└──────────────────────────┘
For example, Duplex=DuplexNoTumble BRPrintQuality=Color InputSlot=Tray2
becomes:
<</Duplex true /Tumble false>>setpagedevice
<</BRProcessColor 4 /BRHrc 0>>setpagedevice
<</ManualFeed false /BRTraysw false /BRFeeder 1>> setpagedevice
Layout
| Path | Purpose |
|---|---|
Containerfile |
Image definition (Debian base, CUPS + cups-filters + Avahi, BR-Script3 PPD) |
cupsd.conf |
Container CUPS configuration (listen on all interfaces, share printers) |
entrypoint.sh |
Starts D-Bus, Avahi and CUPS, then registers the printer queue |
assets/Brother HL-4150CDN/ppd/ |
Official BR-Script3 PPD used for the queue |
assets/Brother HL-4150CDN/Windows/ |
Vendor Windows driver the PPD was extracted from |
Prerequisites
- Podman (or a compatible builder such as Docker/Buildah)
- The printer reachable on the network, with a known IP address or hostname
Build
Run from the repository root:
podman build -t printserver:latest .
The image is architecture-independent (no i386 or other 32-bit binaries), so it builds natively on x86_64 and arm64 hosts alike.
Run
mDNS does not work through a normal container network, so use host networking:
podman run -d --name printserver \
--network host \
-e PRINTER_IP=192.168.100.7 \
-e PRINTER_NAME=HL-4150CDN \
printserver:latest
Environment variables
| Variable | Default | Description |
|---|---|---|
PRINTER_IP |
(empty) | IP address / host name of the network printer. A device URI is derived from it (see PRINTER_PROTOCOL). |
PRINTER_URI |
(empty) | Full device URI. Overrides PRINTER_IP when set. |
PRINTER_PROTOCOL |
socket |
Protocol used with PRINTER_IP: socket, lpd or ipp. |
PRINTER_NAME |
HL4150CDN |
CUPS queue name. Also used as the name advertised via AirPrint/DNS-SD. |
PRINTER_DESCRIPTION |
(empty) | CUPS printer description (lpadmin -D), shown in the CUPS web UI. |
PPD_FILE |
/usr/share/cups/model/BR4150_2.PPD |
PPD used for the queue. |
No queue is created when neither PRINTER_IP nor PRINTER_URI is set.
Choosing the connection
The HL-4150CDN accepts PostScript directly, so the default is raw AppSocket.
Set PRINTER_IP and let the entrypoint build the URI:
PRINTER_PROTOCOL |
Resulting URI |
|---|---|
socket (default) |
socket://<ip>:9100 — raw AppSocket/JetDirect |
lpd |
lpd://<ip>/binary_p1 — LPR/LPD |
ipp |
ipp://<ip>/ipp/print |
For example, a printer at 192.168.100.7:
-e PRINTER_IP=192.168.100.7
Or specify the URI explicitly for full control:
-e PRINTER_URI=socket://192.168.100.7:9100
Advertised name
PRINTER_NAME is used as the CUPS queue name and as the name clients see in
AirPrint discovery. To show a descriptive label in the CUPS web interface,
set PRINTER_DESCRIPTION (this does not change the advertised name):
-e PRINTER_NAME=HL4150CDN -e PRINTER_DESCRIPTION="Brother HL-4150CDN (Office)"
Printing options
The BR-Script3 PPD exposes the HL-4150CDN's hardware options. Common ones:
| Option | PPD keyword | Values |
|---|---|---|
| Paper source | InputSlot |
AutoSelect, Tray1, Tray2, MPTray |
| Two-sided printing | Duplex |
None, DuplexNoTumble (long edge), DuplexTumble (short edge) |
| Colour / mono | BRPrintQuality |
Auto, Color, Black |
| Colour mode | BRColorMode |
Normal, Vivid, None |
| Media type | BRMediaType |
Plain, Thin, Thick, Recycled, Glossy, Label, envelopes, … |
| Print quality | CAPT |
Fine (600×600), SuperFine (2400 dpi class) |
| Toner save | TonerSaveMode |
True, False |
| Secure print | BRJobHold |
None, Private (+ BRJobPIN) |
They can be selected per job, e.g.:
podman exec printserver lp -d HL4150CDN \
-o Duplex=DuplexNoTumble -o BRPrintQuality=Color -o InputSlot=Tray1 report.pdf
Client support
| Platform | Path | Notes |
|---|---|---|
| macOS | AirPrint / IPP Everywhere | Discovered via mDNS; no driver needed |
| iOS / iPadOS | AirPrint | Discovered via mDNS; no driver needed |
| Linux | IPP / IPP Everywhere | Native or with the shared PPD |
| Windows | IPP or raw 9100 | Install Brother's BR-Script3 Windows driver, pointed at the printer or the shared queue |
Windows uses a client-side Brother driver; the server does not share via SMB.
Verify
Browse the CUPS web interface:
open http://localhost:631/ # printers
open http://localhost:631/admin # administration
From the host, or inside the container:
podman exec printserver lpstat -t # queues and status
podman exec printserver lpstat -v HL4150CDN # device URI
podman exec printserver lpoptions -p HL4150CDN -l # available options
podman exec printserver avahi-browse -rt _ipp._tcp # mDNS advertisement
A print job can be submitted with:
podman exec printserver lp -d HL4150CDN /etc/hostname
On macOS/iOS the shared queue should appear in the printer list via AirPrint.
Notes and limitations
-
Host networking is required for Avahi to send and receive mDNS traffic.
-p 631:631alone is enough for direct IPP access but not for AirPrint discovery. -
Printer state is not persisted by default. The queue is recreated on every start from
PRINTER_IP/PRINTER_URI, so nothing has to survive a restart. To keep CUPS state (extra queues, subscriptions) across container replacement, mount a volume over/etc/cups— use a named volume:-v printserver-cups:/etc/cupsPodman seeds a new named volume with the image's
/etc/cups, socupsd.confis present. A bind mount to an empty host directory does not: it hides the image's configuration,cupsdthen exits immediately (status 1, silently — its defaultErrorLogis syslog), the container stops, and the entrypoint'slpadminfails with a misleadinglpadmin: Unable to connect to server: Bad file descriptor. This is easiest to hit from a quadlet that bind-mounts a share, e.g.Volume=/nfs/share/print-server/:/etc/cups/:Z. If you must bind-mount, make sure the directory already contains acupsd.conf; also note that a read-only share, NFSroot_squashor SELinux relabeling can still preventcupsdfrom writing its state there. For a named volume in a quadlet, useVolume=printserver-cups:/etc/cups(a bare name, no leading/). -
Colour handling is done by the printer's own PostScript RIP. Output may differ slightly from Brother's host-side (rasterising) driver.
-
The queue advertises AirPrint capabilities (
image/urf,image/pwg-raster,sides-supported,color-supported). macOS and iOS printing is converted by cups-filters; verify end to end on your network.
Troubleshooting
The container runs but nothing answers on 631
The queue can be advertised over mDNS while nothing is actually listening, so clients discover a printer they cannot reach. Check inside the container:
podman exec printserver curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:631/
podman exec printserver tail -20 /var/log/cups/error_log
Unable to open listen socket for address 0.0.0.0:631 - Permission denied —
cupsd never bound port 631. This is the normal outcome of rootless Podman
with Network=host: port 631 is privileged, and a rootless container does not
hold CAP_NET_BIND_SERVICE in the host network namespace (adding the capability
does not help, because the capability must belong to the host user namespace).
cupsd keeps running on its domain socket, so lpadmin/lpstat inside the
container still work — which makes the failure easy to miss. Fix one of:
-
allow unprivileged binding of port 631 on the host, persistently (needs root):
echo 'net.ipv4.ip_unprivileged_port_start=631' | sudo tee /etc/sysctl.d/90-unprivileged-ports.conf sudo sysctl --system -
run the container as a rootful (system) service rather than a user quadlet;
-
or serve on an unprivileged port (CUPS advertises the actual port in its SRV record, but be aware some AirPrint clients assume 631).
On SELinux systems, verify there is no port-labelling denial as well
(sudo ausearch -m avc -ts recent). Also ensure 631/tcp and 5353/udp are open in
the firewall.
macOS/iOS sees the printer but reports it cannot connect
If plain IPP works but the secure connection fails, this is the _ipps
service: macOS prefers _ipps._tcp, which CUPS advertises unconditionally
(also when no certificate exists). CUPS creates its TLS certificate lazily on
the first TLS connection, in /etc/cups/ssl — so that directory must be
writable. If it is not, CUPS logs
Unable to encrypt connection: Unable to create server credentials and the
secure connection fails while plain IPP keeps working. Use a writable (named)
volume for /etc/cups; see the bind-mount caveat under Notes and
limitations. After changing anything, remove the printer in macOS and add it
again — macOS caches the failed attempt.
CUPS 3 and the future of PPDs
Adding the queue with a PPD makes lpadmin print:
lpadmin: Printer drivers are deprecated and will stop working in a future version of CUPS.
This is an advance notice, not an error. OpenPrinting deprecated PPDs, printer
drivers and filter executables during the CUPS 2.x series, and CUPS 3.x drops
them entirely in favour of an all-IPP architecture (a local server for
temporary driverless queues plus a sharing server for persistent ones, with
filtering limited to PDF, PWG Raster, Apple Raster and PCLm). Any queue created
with a PPD (-P) or a driver model (-m <driver>) triggers the message — it is
not specific to Brother, and there is no way to keep the PPD and silence it. A
raw queue and -m everywhere (driverless) do not print it.
The driverless replacement is a Printer Application: a PAPPL-based service
that emulates a driverless IPP printer and does the format/option conversion on
the other side. For a PostScript device like the HL-4150CDN that role is filled
by the PostScript Printer Application (ps-printer-app), which takes over
exactly what our BR-Script3 PPD does today — the PPD does not disappear, it just
moves out of CUPS and into the printer application.
Note the HL-4150CDN is itself not IPP Everywhere / AirPrint capable (Brother
lists it as such), so the plain -m everywhere shortcut is not available here.
This container instead advertises AirPrint to its clients and performs the
PostScript conversion itself.
Nothing needs to change right now: the distro images used here ship CUPS 2.4.x
and PPD-based drivers keep working for years. When you eventually move to
CUPS 3.x, run ps-printer-app (or legacy-printer-app) alongside CUPS and
create the queue driverlessly against it (ipp://…), leaving the PPD inside the
printer application.