# 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: ```postscript <>setpagedevice <>setpagedevice <> 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, generates the `_ipps` certificate, 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: ```sh 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**: ```sh 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 the base of the advertised AirPrint/DNS-SD name (` @ `) — while no Description is set; `PRINTER_DESCRIPTION` overrides the advertised name. | | `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. | | `PRINTER_OPTIONS` | *(empty)* | Space-separated PPD option assignments (`key=value`) applied as queue defaults on every start, e.g. `BRColorMode=Vivid TonerSaveMode=False`. Discover the names with `lpoptions -p -l` — but note that this command mis-renders Boolean options; see below. | | `CERT_EXTRA_NAMES` | *(empty)* | Comma-separated extra DNS names and/or IP addresses to put into the generated `_ipps` certificate. | | `CERT_DAYS` | `3650` | Validity of the generated certificate, in days. | No queue is created when neither `PRINTER_IP` nor `PRINTER_URI` is set. `PRINTER_OPTIONS` sets queue defaults that survive the recreation of the queue, because they are re-applied on every start: ```sh podman run -d --name printserver \ --network host \ -e PRINTER_IP=192.168.100.7 \ -e PRINTER_OPTIONS="BRMediaType=Plain BRColorMode=Vivid TonerSaveMode=False" \ printserver:latest ``` An applied assignment wins over the PPD's own default for that option: a job that does not select it itself is printed with the configured value, which shows up in the PostScript the queue sends to the printer (e.g. `%%BeginFeature: *TonerSaveMode False`). Verify it with `tests/test-queue-defaults.sh`. Do not read a queue default back from `lpoptions -p -l`: for Boolean options it marks the *False/Off* entry instead of the current value, so a queue default of `TonerSaveMode=True` still shows as `*False`. `BRColorMode` (a `PickOne`) reflects correctly with `*Vivid`, and the stored setting is always visible in `/etc/cups/ppd/.ppd` as `*DefaultBRColorMode: Vivid` / `*DefaultTonerSaveMode: True`. Entries that the PPD does not know are logged as a warning and ignored; the remaining ones are applied and the container starts normally. ### 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://:9100` — raw AppSocket/JetDirect | | `lpd` | `lpd:///binary_p1` — LPR/LPD | | `ipp` | `ipp:///ipp/print` | For example, a printer at `192.168.100.7`: ```sh -e PRINTER_IP=192.168.100.7 ``` Or specify the URI explicitly for full control: ```sh -e PRINTER_URI=socket://192.168.100.7:9100 ``` ### Advertised name `PRINTER_NAME` is the CUPS queue name. It is also the base of the name clients see in AirPrint discovery — CUPS advertises `" @ "` — but only as long as no Description is set: `PRINTER_DESCRIPTION` (the `lpadmin -D` Description) has precedence and becomes the advertised name instead: ```sh -e PRINTER_NAME=HL4150CDN -e PRINTER_DESCRIPTION="Brother HL-4150CDN (Office)" ``` advertises the DNS-SD instance `Brother HL-4150CDN (Office) @ `, not `HL4150CDN @ `. That instance name is what clients pin into their `dnssd://` device URI, so every queue needs a **unique** Description: two queues deriving the same name collide, and only one of them is ever advertised (see *macOS shows the printer offline after some time* under Troubleshooting). ### TLS certificate for `_ipps` CUPS advertises the queue twice over mDNS: `_ipp._tcp` (plain) and `_ipps._tcp` (TLS). macOS and iOS prefer the secure one, and *validate the certificate against the name they connected to*. The name clients see is Avahi's `.local`, which is not necessarily the container's host name — so the entrypoint installs its own self-signed certificate instead of leaving it to CUPS. Its SANs cover the container host name, `.local`, Avahi's FQDN, `localhost`, `127.0.0.1` and everything in `CERT_EXTRA_NAMES`: ```sh -e CERT_EXTRA_NAMES=192.168.100.20,print.example.org ``` The certificate lives in `/etc/cups/ssl` (so that directory must be writable: use a named volume, see the bind-mount caveat under *Notes and limitations*) and is only regenerated when one of the names it must cover is missing, so a restart normally logs `TLS certificate is up to date`. It is also copied — as `.crt`/`.key` — for every name CUPS may look it up by, because CUPS 2.4 does not choose the certificate by the name the client requested: it reverse-resolves the *local* address the client connected to and loads `.crt` from `/etc/cups/ssl`. A host-networked container resolves its own LAN address to the name Podman writes into `/etc/hosts` (`host.containers.internal`), and IPv6 literals are used as-is, so those names need a copy too. Otherwise CUPS mints a certificate of its own for the address, whose SANs do not cover the advertised `.local` name. Inspect what the server actually presents: ```sh podman cp printserver:/etc/cups/ssl/server.crt . openssl x509 -in server.crt -noout -subject -ext subjectAltName echo | openssl s_client -connect localhost:631 -servername "$(hostname).local" \ | openssl x509 -noout -subject -ext subjectAltName ``` Because the certificate is self-signed, a fully validating client (macOS/iOS AirPrint, `curl` without `-k`) rejects it until you trust it. CUPS' own tools (`lpadmin`, `ipptool`) are lenient, so plain printing works without this step: - **macOS** (needs your password): ```sh sudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain ./server.crt ``` - **iOS/iPadOS**: AirDrop or mail `server.crt` to the device, install the profile, then enable full trust under *Settings → General → About → Certificate Trust Settings*. Alternatively, keep using plain IPP (port 631 without TLS) — printing and duplex/colour options work there too. ## 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` (default), `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.: ```sh podman exec printserver lp -d HL4150CDN \ -o Duplex=DuplexNoTumble -o BRPrintQuality=Color -o InputSlot=Tray1 report.pdf ``` ### Why the macOS/iOS dialog shows fewer options macOS and iOS print driverless (AirPrint / IPP Everywhere), so the dialog shows only IPP-standard options: media size, duplex, pages per sheet, the black-and-white/colour toggle and the quality slider. Brother-specific options (`BRColorMode`, `BRMediaType`, `TonerSaveMode`, `CAPT`, …) have no IPP equivalent and can never appear there. Set them server-side instead: - as queue defaults at container start via `PRINTER_OPTIONS` (see *Environment variables*), - per job with `lp -o …`, or - interactively in the CUPS web UI (`http://:631/admin`, *Printing Options*). The dialog's black-and-white/colour toggle maps to `print-color-mode` and the quality slider to `print-quality`; the PPD presets documented below translate both onto the Brother options, so they now really take effect: | Dialog / IPP | PPD option it becomes | | --- | --- | | `print-color-mode=monochrome` | `BRPrintQuality=Black` | | `print-color-mode=color` | `BRPrintQuality=Auto` | | `print-quality=3` (draft) | `TonerSaveMode=True` | | `print-quality=4` (normal) | defaults (`Fine`, toner save off) | | `print-quality=5` (high) | `CAPT=SuperFine` (2400 dpi class) | Client-specified values always win over the preset. ### Changes to the vendor PPD `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD` is Brother's original file plus one addition, marked with a `%` comment in the file: - **Six `APPrinterPreset` entries** mapping `print-color-mode` (color/monochrome) and `print-quality` (draft/normal/high) onto `BRPrintQuality`, `CAPT` and `TonerSaveMode`. CUPS applies such presets for driverless clients and always lets the client's own option values win. In particular `print-color-mode=monochrome` becomes `BRPrintQuality=Black`, so black-and-white printing works: the server cannot do the greyscale conversion itself, because `pdftops` uses Poppler for Brother printers and Poppler cannot produce greyscale (`WARNING: Grayscale/monochrome could be printed as color with Poppler` in the CUPS log). The presets use the notation CUPS generates itself — Apple metadata as `key value` pairs *without* `=`, PPD options as `*Name Value` pairs on separate lines inside a multiline `"…"` string. With `key=value` on a single line CUPS silently ignores the whole preset (verified against libcups 2.4.2: `_ppdParseOptions()` splits on whitespace, so `key=value` becomes the option *name* and the lookup fails). Note that IPP `print-quality` is `3` (draft), `4` (normal), `5` (high). Every other PPD value, including the colour-mode default `BRColorMode: Normal`, is the vendor original — the container does not tune colour output (see [Colours are printed pale / washed out](#colours-are-printed-pale--washed-out)). ## Client support | Platform | Path | Notes | | --- | --- | --- | | macOS | AirPrint / IPP Everywhere | Discovered via mDNS; no driver needed. If *Use:* shows *Generic PostScript Printer*, see [Troubleshooting](#macos-adds-the-printer-as-a-generic-postscript-printer) | | 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: ```sh open http://localhost:631/ # printers open http://localhost:631/admin # administration ``` From the host, or inside the container: ```sh 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: ```sh 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:631` alone 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**: ```sh -v printserver-cups:/etc/cups ``` Podman seeds a *new named volume* with the image's `/etc/cups`, so `cupsd.conf` is present. A **bind mount to an empty host directory does not**: it hides the image's configuration, `cupsd` then exits immediately (status 1, silently — its default `ErrorLog` is syslog), the container stops, and the entrypoint's `lpadmin` fails with a misleading `lpadmin: 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 a `cupsd.conf`; also note that a read-only share, NFS `root_squash` or SELinux relabeling can still prevent `cupsd` from writing its state there. For a named volume in a quadlet, use `Volume=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: ```sh 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): ```sh 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). The certificate lives in `/etc/cups/ssl`, so that directory must be writable — use a **writable (named) volume**; see the bind-mount caveat under *Notes and limitations*. If it is not writable, CUPS logs `Unable to encrypt connection: Unable to create server credentials` and the secure connection fails while plain IPP keeps working. The other cause is the certificate itself: clients only accept it when the name they connected to is in its SANs *and* they trust it (see *TLS certificate for `_ipps`*). Typical symptoms: `curl https://.local:631/` ends with `(60) SSL certificate problem`, or `ipptool -tv ipps://…` succeeds while macOS/iOS refuse. Add the missing name via `CERT_EXTRA_NAMES` or trust the certificate. After changing anything, remove the printer in macOS and add it again — macOS caches the failed attempt. ### macOS adds the printer as a Generic PostScript printer Symptom: the queue prints, but *Use:* shows **Generic PostScript Printer** and the print dialog offers no duplex, no colour and no tray 2 — while iOS, which queries the printer live, offers all of them. On the Mac, `lpoptions -p -l` lists only `APOptionalDuplexer` plus `Duplex`/`PageSize`/`ManualFeed`/`InputSlot`, and `/etc/cups/ppd/.ppd` contains `*PCFileName: "Generic.ppd"`. macOS only takes its driverless AirPrint path when it accepts the printer's secure `_ipps` service; if that fails it silently falls back to its bundled generic PostScript driver. Duplex then stays hidden, because the generic PPD gates it behind `APOptionalDuplexer False`. There are two causes; check the Bonjour sub-types first. **1. The printer is advertised with the CUPS `_cups` sub-type.** macOS classifies a Bonjour printer as *shared* when it can be browsed under `_ipp._tcp,_cups`, and its AirPrint driver is only offered for a printer that carries `_universal` and no `_cups` (`-[DNSSDPrinter isShared]` / `-[DNSSDPrinter airprintMatch]` in `AddPrinter.app`'s `PMBrowser.framework`). CUPS registers `_cups,_print,_universal` unless `BrowseDNSSDSubTypes` says otherwise, so the queue is offered with the generic driver only. `cupsd.conf` therefore sets `BrowseDNSSDSubTypes _print,_universal`. Confirm it is in effect: ```sh podman exec printserver avahi-browse -rt _ipp._tcp,_universal # lists the queue podman exec printserver avahi-browse -rt _ipp._tcp,_cups # must not list it ``` The second command lists other CUPS servers on the network; only *this* queue must be absent from it. If it does list the queue, the image's `cupsd.conf` did not reach `/etc/cups` — a bind-mounted `/etc/cups` hides the image's copy; see *Notes and limitations*. **2. The certificate is rejected.** Clients only accept it when the name they connected to is in its SANs *and* they trust it (see *TLS certificate for `_ipps`*). Check what is advertised and served: ```sh podman exec printserver avahi-browse -rt _ipps._tcp # what is advertised podman cp printserver:/etc/cups/ssl/server.crt . && openssl x509 -in server.crt -noout -subject -ext subjectAltName ``` A way around either cause is to add the queue from the command line, driverless over the same secure service (`ipptool -tv ipp://:631/printers/ ` prints the UUID; drop the `urn:uuid:` prefix): ```sh sudo lpadmin -x sudo lpadmin -p -E -m everywhere \ -v "dnssd://%20%40%20._ipps._tcp.local./?uuid=" lpoptions -p -l # Duplex, ColorModel and tray 2 show up now ``` In the GUI, remove the printer and add it again, then pick **AirPrint** (not *Generic PostScript Printer*) under *Use*. If only the generic entry is offered, either the `_cups` sub-type is still being advertised or the secure connection is still being rejected — fix that, or add the queue from the command line as above. macOS caches the failed attempt, so remove and re-add the printer after changing anything on the server. ### Colours are printed pale / washed out Symptom: colour printouts through the container look desaturated compared to the same PDF printed through the Mac's local (Brother) driver. Cause: a known limitation, not a switchable setting. Tested side by side on the HL-4150CDN, the PPD colour-mode options make **no visible difference**: `BRColorMode None` — which switches the printer's CIE conversion off via `<>` — prints the same as the vendor default `Normal`, and `Vivid`, `BRPrintQuality` (`BRColorAdapt`) and `TonerSaveMode` change nothing either. (An earlier version of this container defaulted to `None` on the theory that the printer's CIE conversion caused the washout; before/after test prints disproved that, and the default is the vendor original again.) The gap comes from the conversion path: the local driver applies its own client-side colour management, while the container converts the job server-side to PostScript without it. No server-side lever is currently known. For colour-critical output, print through the local driver. The remaining server-adjacent checks are: ```sh podman exec printserver lpoptions -p -l | grep BRColorMode # expect *Normal podman exec printserver lp -d -o BRColorMode=Vivid test.pdf # optional experiment ``` Also check the printer's own menu (Toner → Toner Save off) and make sure `BRMediaType` matches the media in the tray — both affect the print, though neither closes the saturation gap. Black-and-white is a related but separate path: `print-color-mode=monochrome` from a driverless client is mapped by the PPD presets onto `BRPrintQuality=Black` (the printer renders mono). It is deliberately *not* done by the server — with its Poppler renderer, `pdftops` cannot convert to greyscale. Forcing Ghostscript with `-o pdftops-renderer=gs` would, but that path is not recommended for Brother printers (bugs in their PostScript interpreters). ### macOS shows the printer offline after some time Symptom: the printer was added and printed fine, then macOS reports it *offline* ("printer is not responding") some hours later — typically after the container was restarted or after queues were edited on the server. Restarting the container does not help; removing the printer on the Mac and adding it again fixes it — until it happens again. Cause: when macOS adds a Bonjour printer it pins the exact DNS-SD **instance name** into the queue's device URI: ```text dnssd://Laserdrucker%20HL-4150CDN%20%40%20podp._ipps._tcp.local./?uuid=… └── " @ " — must still resolve hours later ``` CUPS derives that name from the **Description**, not from the CUPS queue name (the queue name is only the base while no Description is set). Two queues with the same Description therefore derive the same instance name. Avahi can register only one service per name, and CUPS 2.4 does not rename on collision (upstream `scheduler/dnssd.c` still carries a `TODO` for it), so the losing queue stays *Shared* but is never advertised. The Mac keeps resolving the name that no longer exists and flips the queue to offline. The lost race is deterministic — the same queue wins after every restart — which is why restarting the container never helps, while remove/re-add re-pins the Mac to the instance that is actually advertised. The entrypoint logs a warning at startup when it finds such a collision. To check by hand: ```sh podman logs printserver 2>&1 | grep 'DNS-SD name' # entrypoint warning, if any lpstat -v | grep dnssd # the instance name the Mac pinned podman exec printserver lpstat -l -p # duplicate Descriptions? dns-sd -B _ipp._tcp # one instance per name? podman exec printserver avahi-browse -rt _ipps._tcp # what is really advertised ipptool -tv ipp://:631/printers/ \ /usr/share/cups/ipptool/get-printer-attributes.test # server side itself ``` Two queues showing the same `Description:` while `dns-sd -B` lists only one instance is the collision. Fix it on the server: give each queue a unique Description, or remove the redundant queue: ```sh lpadmin -h :631 -p laserdrucker-im-buero -D "Laserdrucker Buero" # or drop the duplicate entirely: lpadmin -h :631 -x laserdrucker-im-buero ``` Then remove the printer on the Mac and add it again — macOS re-pins the device URI to the instance that is advertised now. The same remove/re-add is needed after **every** Description change: the pinned name changes with it, and a container restart alone cannot bring the old name back. ## 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 `) 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.