hanxi/cups-web: a self-hosted web interface for CUPS print queues
CUPS Web is a self-hosted web interface for managing printers and print queues with CUPS.
At a glance
- What is it?
- cups-web wraps CUPS in a Go and Vue browser UI for uploading files, submitting jobs and managing drivers. It targets home and small-office printers, and the driver install path is where most of the real complexity lives.
- Who is it for?
- Adopt cups-web if you run CUPS on a home or small-office network and want browser uploads, per-user print records and driver installation without touching lpadmin. Do not adopt it if you cannot give the container root, cannot mount /dev/bus/usb, or need to keep the admin account out of reach of untrusted users, since the README states that installing an uploaded .deb runs package scripts as root.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What cups-web adds on top of CUPS
CUPS already has a web interface, and the search data shows people asking how to enable it and where it lives. cups-web is a separate application that talks to CUPS over IPP rather than a replacement for that admin page. The README describes it as a browser-based print management tool built on CUPS, aimed at homes and small offices.
The gap it fills is the submission side. CUPS accepts jobs; it does not give a household a login page where each person uploads a file, picks a printer, sets duplex and copies, and then sees their own history. cups-web adds that layer plus an admin backend with user management, print records, and a retention policy that deletes expired records and their files on an hourly sweep.
The stack is a single Go binary serving a Vue 3 frontend built with Vite. State lives in SQLite through modernc.org/sqlite, the pure-Go driver, so there is no CGO dependency. IPP traffic goes through OpenPrinting/goipp. That combination is what makes the all-in-one container possible.
How a print job flows from browser to spooler
The upload endpoint accepts PDF, images (JPG, PNG, GIF, HEIC), Office formats, OFD and plain text. Not every format reaches CUPS unchanged. Office documents are converted to PDF through LibreOffice. OFD files go through a bundled Java converter built on ofdrw. Text and images are rendered to PDF server-side with gofpdf and pdfcpu, and selecting several images at once merges them into one PDF.
That conversion step is the design decision worth noticing. It means the container carries LibreOffice and a Java 21 runtime, which is most of the image size, but it also means the client never needs a driver or a plugin. A phone browser can submit a docx.
Preview follows the same path: the README lists PDF preview, a visual preview of paper orientation, and page-count estimation. Once a job is submitted, cups-web records the file, page count, copies, duplex and colour options, and status in SQLite, which is what the admin query filters on later.
Authentication is session-based with Gorilla securecookie, encrypted and signed, with the key generated and persisted in the database. Non-GET requests carry an X-CSRF-Token header. Passwords are stored with bcrypt.
Installing cups-web with Docker Compose
The recommended deployment is Docker, and the repository ships a docker-compose.yml. The README also shows downloading that file directly rather than writing it by hand.
wget https://raw.githubusercontent.com/hanxi/cups-web/master/docker-compose.yml
docker compose up -dOptional settings go in a .env file next to the compose file. The defaults are print/print, which is fine for a first run on a trusted network and not fine for anything else.
CUPSADMIN=print
CUPSPASSWORD=your_cups_password
TZ=Asia/ShanghaiThe compose file in the repository uses host networking, because mDNS and DNS-SD need multicast on 5353/udp and a bridge network blocks it. The consequence is that the CUPS port is fixed at 631 on the host and the web port is set by LISTEN_ADDR, defaulting to :1180. The README's older example maps 631 and 1180 explicitly; the current compose file does not, because a host network has no port mapping.
Once it is up, the README says to open http://localhost:1180 and log in with admin/admin. The driver page is in the navigation bar and is visible only to administrators. There is also a command-line route for the same operations, which runs synchronously and holds the terminal until it finishes:
docker exec cups driver-list
docker exec cups driver-install canon-ufr2
docker exec cups driver-list --installed
docker exec cups driver-remove canon-ufr2Driver installation is asynchronous and single-threaded
Most printers work with the preinstalled packages: printer-driver-all, cups-pdf, escpr, foo2zjs, brlaser, the foomatic and openprinting PPD collections, hplip, and ipp-usb with CUPS driverless support for IPP Everywhere, AirPrint and Mopria. Vendor drivers such as Canon UFR II, Canon CAPT, Epson ESC/P-R 2 and Konica Minolta bizhub are installed on demand.
The install endpoint returns immediately and the work happens in the background behind a progress card that refreshes every two seconds and streams the build log. The README is explicit that drivers needing compilation can take minutes to tens of minutes, that the page is not frozen, and that refreshing loses the live log, in which case the fallback is docker compose logs -f cups. The backend task timeout is 30 minutes and the page waits up to 35.
Only one driver task runs at a time. The README gives the reason: apt and dpkg hold a global lock, so concurrent installs would fail against each other. A second install, uninstall or combined install-and-add request is rejected while one is running, and uploading a .deb is rejected on the same grounds.
Architecture is a hard gate, not a warning. The table lists canon-ufr2 as amd64 and arm64 only, epson-cn as amd64 only, and escpr2 as amd64, armhf and arm64. When no binary exists for the running architecture, the README states the install button is disabled with an explanation rather than left clickable.
Persistence, root, and the .deb upload risk
Manually installed drivers are snapshotted into a .drivers volume and restored when the container is recreated. The two upload types are restored differently. A .ppd is copied back to /usr/share/cups/model/custom. A .deb is archived under .drivers/custom-deb/packages/ and reinstalled with dpkg -i at startup, because the README notes the real install work happens in the package's own scripts and copying files back would not activate the driver. Reinstalling is skipped when the package is already present at the same or a newer version.
If a .deb never installs, it is retried on every container start with a warning in the log. Removing it means deleting the file from ./.drivers/custom-deb/packages/ on the host.
The security note is the part to read twice. Installing a .deb means dpkg runs the package's install scripts as root, which the README describes as equivalent to executing arbitrary code in the container and modifying its system state. It calls this a deliberate administrator capability and states plainly that the admin password is effectively the container's root credential. Every upload writes the uploader's username to the container log for later audit. The user role cannot see or call the driver endpoints.
Two more constraints from the compose file: the container must run as root, and USB printing depends on that line. CUPS installs the usb backend as 0744 and runs it as root when cupsd's uid is 0. Under a non-root user, as with a Kubernetes runAsUser or docker run -u, the backend drops privileges and the group id of lp inside the container has to match the host udev rule for the USB device. When it does not, the README's compose comments describe the symptom as a queue stuck on "Waiting for printer to become available". The compose file also disables AppArmor confinement to avoid denials on Proxmox LXC and similar systems.
The host avahi conflict and when to pick something else
The compose file explains that host networking is required for mDNS, and that this creates a specific conflict: if the host already runs avahi-daemon, it competes with the container's avahi for port 5353. The host daemon cannot broadcast or discover on behalf of the container's CUPS either, because they sit on separate D-Bus buses. The README's guidance is to stop the host-side avahi before deploying. If you need the host's avahi for other services, this project is the wrong fit.
A second case where it is the wrong tool is a fleet. There is no printer-group policy, no quota enforcement and no queue-level accounting for many sites. The admin backend filters print records by username and time range, which is enough for a household and thin for anything larger.
For a plain CUPS setup, the built-in web interface at port 631 is the alternative, and the difference is scope rather than quality. It manages printers, classes and jobs, and it does not offer per-user accounts, file conversion, or a print history tied to a person. If your users are comfortable with lpadmin and lpr and nobody needs an audit trail, cups-web adds a Java runtime, LibreOffice and a SQLite database for capability you will not use. A lighter middle path is CUPS plus a shared Samba or WebDAV folder that users drop files into, which keeps the driver problem but removes the account layer.
Maintenance, licence and what to check before upgrading
The last push to the repository was on 2026-08-25, the same day as the v0.2.8 release, with v0.2.7 and v0.2.6 arriving earlier that month. The project is not archived. The release cadence in the supplied history is a burst of three versions in five days, which says nothing about the next five months.
Upgrade cost concentrates in the image rather than the application. The Dockerfile builds in five stages, compiling CUPS from OpenPrinting source to overlay the apt version, building the OFD converter with OpenJDK 21 and Maven, and building the Go binary with the frontend embedded. The frontend stage deliberately uses node:20-slim instead of Bun because Bun does not support 32-bit ARM, and the Java stage is pinned to the build platform to avoid running OpenJDK under QEMU on armhf. Pulling a new tag means pulling all of that again.
A version bump can also invalidate manual driver work. The .drivers volume survives image replacement, but a new image may carry a different CUPS build or different PPD paths. The README does not document a rollback procedure for a driver that installs on one version and fails on the next, so keeping the previous image tag available is the practical safeguard.
The licence is MIT, which permits commercial and closed-source use and requires preserving the copyright notice and licence text. That covers cups-web's own code. The image redistributes CUPS, LibreOffice, Java, hplip and vendor driver packages, each under its own terms, and MIT says nothing about those. The README does not enumerate the licences of the bundled driver packages, so anyone redistributing the image should check the package metadata rather than assume the MIT grant extends to everything inside it. This is a description of the licence text, not legal advice.
Editorial conclusion
Adopt cups-web if you run CUPS on a home or small-office network and want browser uploads, per-user print records and driver installation without touching lpadmin. Do not adopt it if you cannot give the container root, cannot mount /dev/bus/usb, or need to keep the admin account out of reach of untrusted users, since the README states that installing an uploaded .deb runs package scripts as root. Before committing, verify that your printer is covered by the preinstalled printer-driver-all set, that your host does not already run avahi-daemon on port 5353, and that you can back up the ./.drivers volume, because deleting it loses every manually installed driver.
Frequently asked questions
How do I access the cups-web interface from a browser?
Open http://localhost:1180 and log in. The README gives the default credentials as admin/admin, and the current compose file sets the listening address through LISTEN_ADDR, which defaults to :1180.
What is the cups-web interface?
It is a self-hosted web interface for managing printers and print queues through CUPS, written in Go with a Vue frontend. The README describes it as a browser-based tool for uploading files and submitting print jobs remotely, with multi-user management and print record tracking.
How do I enable the cups-web interface?
Deploy it with Docker: download the repository's docker-compose.yml and run docker compose up -d. The README recommends this route over the binary deployment, which is for setups that already have a CUPS service.
How to access CUPS from browser?
For cups-web, the README points to http://localhost:1180 after docker compose up -d. The CUPS service itself is exposed on port 631, which is the port the built-in CUPS web interface uses.
What does CUPS stand for in Linux?
cups-web is built on CUPS, which the README links to at cups.org and describes as the print service the project manages. The README does not expand the acronym anywhere in the text.
What is the latest version of CUPS?
The README states that the image compiles CUPS 2.4.x from source to overlay the apt version. It does not name a specific upstream release number, so the answer for a given image is whatever that build stage pulled.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hanxi-cups-web)