docker-registry-ui: a browser front end for a private registry, and nothing more
The simplest and most complete UI for your private docker registry v2 and v3
At a glance
- What is it?
- Joxit's project puts a browsable, deletable view on top of a Docker Registry v2 or v3 instance, shipped as a static bundle behind nginx.
- Who is it for?
- The reason this project has held 3,534 stars is that it refuses to be more than it is. There is no database, no user store and no session handling to attack, because there is no server-side code at all; the container is nginx serving a rollup bundle that talks straight to your registry with the browser's own credentials.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 57 days ago.
- What is it written in?
- Mainly Riot, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
No backend, just nginx serving a rollup bundle
The single most important fact about docker-registry-ui is that it has no backend. The Dockerfile is fifteen lines and starts from an nginx base image:
FROM nginx:alpine-slim
WORKDIR /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh
COPY dist/ /usr/share/nginx/html/There is an entrypoint script that runs on container start, a config file, and a `dist/` directory of compiled assets. No application server, no database, no session store. The `package.json` confirms the shape on the build side: version 2.6.0, an ES module project, with rollup doing the bundling and mocha running the tests.
The consequence for a reader is that every feature has to be implemented in the browser against the registry's HTTP API. That is a real constraint and it explains most of the project's limits. GitHub classifies the repository language as Riot, which is the micro-library the interface is written in, described in the README as a react-like user interface micro-library, paired with riot-mui for components. There is no server code to read, so the interesting logic is in how carefully the client handles credentials, redirects and CORS.
Tag naming is the release channel
The published image tags follow a scheme that has grown a few appendices over the years, and reading it tells you exactly what you are installing:
ENV NGINX_PROXY_HEADER_Host '$http_host'
ENV NGINX_LISTEN_PORT '80'
ENV SHOW_CATALOG_NB_TAGS 'false'The `latest` tag is the latest release on nginx:alpine, `latest-debian` is the same release on nginx:debian, and `main` or `master` is the beta build. The `2` tag tracks the latest 2.x release including minor and patch, `2.x` tracks the latest patch inside a minor line, and `2.x.y` pins an exact release. So the release channel is expressed entirely in the tag you pull.
The base image choice is not decorative. Release 2.6.0, published on 2026-01-19, includes a fix for the new unprivileged nginx images, which is the kind of change that only matters to people who deploy on Kubernetes with a non-root security context. The tree carries `arm32v7.dockerfile` and `arm64v8.dockerfile` next to the main `Dockerfile`, so single-board computers and ARM cloud instances are first class rather than an afterthought. Multi-architecture support also shows up in the interface itself: the history page has shown multi-arch manifests since version 1.5.0.
Deleting tags is where the keyboard shortcuts live
The README keeps its most interesting material under a heading called hidden features, and the density of it is the argument for the project. Multi-select deletion arrives in three layers, each tied to a version: checkboxes for picking several tags landed in 1.2.0, alt-clicking the indeterminate checkbox to select the whole page came in 1.2.1, and shift-clicking between two tags to select a contiguous range came in 2.4.0. Each one references an issue and a pull request, so the reasoning is recoverable.
The rest of the list is small but real. Hovering a tag reveals its sha256. The tag list sorts with numeric awareness, so v2 comes before v10 instead of after it, which has been there since 0.4.0. The search bar filters both images and tags, takes focus with CTRL+F or F3, and falls back to the browser default when it is already focused, a small courtesy added in 2.1.0. Since 2.4.0 the interface can show the contents of a Dockerfile. It also caches registry responses such as blobs and some manifests, which it identifies by URL containing `sha256:`.
That cache is the one item with a security dimension, and the README does not discuss it. If you delete a tag and the UI still shows it, that cache is the first place to look.
Basic auth is the only authentication, and CORS is your problem
The FAQ section is the most honest part of the README, because it is a list of things the project cannot do. Authentication is basic auth only, and the phrasing is worth reading twice: it is a simple standalone frontend, it will use your browser window for authentication. There is no user database, no roles and no token issuance. Whoever can reach the page can see everything the registry account behind the browser prompt can see.
HTTPS is somebody else's job. Put your favourite reverse proxy in front of the UI and let it terminate the connection. That single decision causes most of the reported problems, because a browser will refuse HTTP content on an HTTPS page, which is what the Mixed Content error in the FAQ is describing. Upgrade the registry to HTTPS or serve the UI over plain HTTP, but do not mix them.
The CORS failures have their own FAQ entry, covering the case where an OPTIONS preflight or a DELETE returns 401 and the interface suggests checking `Access-Control-Allow-Origin`. The default nginx `Host` header is set to `$http_host` rather than the usual value, and the README explains why: it fixes issue 88, with more detail in issue 113. If you are putting this behind your own proxy you will need to reproduce that, and if you use the public demo against a private registry you are told to set `Access-Control-Allow-Origin` to `https://joxit.dev`, which is worth pausing on before you do.
Some confusing behaviour belongs to the registry, not the UI
One FAQ entry is a gift for anyone who has ever been confused by this class of tool. When you delete every tag of an image and the image is still listed, the answer is that this is a limitation of the Docker registry: the garbage collector does not remove empty images. If you want the dangling images gone you have to delete the folder in your registry data. The README links issue 77 for the long-running version of that conversation.
A second entry explains a discrepancy that looks like a bug in the size column. The interface shows the compressed size of the image, not the extracted size that `docker images` reports. Two different numbers for the same artefact, both correct.
The third is about registries served over plain HTTP. Yes, the UI and the Docker client can both talk to an insecure registry, but you have to configure the Docker client first, again with a link to the tracking issue. None of these are things the project could fix, and saying so plainly is more useful than a feature request thread.
One option is worth calling out on its own. `SINGLE_REGISTRY` is the major configuration knob: set it to false and a menu appears in the interface for dynamically changing registry URLs, which is how the public demo serves many registries from one page. Set it to true and the picker disappears, which is the behaviour the old `static` tag used to have.
Riot, a pinned component fork, and a surprising amount of polish
Release 2.6.0 is the most recent one listed, and it reads like a maintenance release from a mature project rather than a burst of features. It adds `DOCKER_REGISTRY_UI_TITLE` and `ENABLE_VERSION_NOTIFICATION` so a self-hosted instance can identify itself, adds a `/version.json` endpoint, adds `SHOW_TAG_HISTORY` to hide the tag history button, and adjusts contrast in both the light and dark themes. The bug fixes are of the kind that only surface in real deployments: avoiding exceptions and showing a date when using OCI images, and a request header that got too big when a cookie was defined.
The dependency list is where the age shows. `riot` sits at 9.x and `riot-mui` is not the upstream package at all but a GitHub reference pinned to a commit in Joxit's own fork, `github:joxit/riot-5-mui`. Building an Electron wrapper is a supported path via `build:electron`, and the `examples/` directory in the tree holds both the Electron project and deployment examples. `Developing.md` exists for contributors, and `rollup.config.js` with a `rollup/` directory suggests the build has been customised well past the default.
The last push was on 2026-08-10, seven months after 2.6.0 shipped, with 42 open issues and 368 forks. The project sits under AGPL-3.0, which matters here more than it might for a static frontend: the copyleft obligation attaches when you serve a modified version over a network. For internal use at a company where nobody is offering the tool as a service, that is rarely a practical problem, but it is a deliberate choice by the author and the badges say so.
Editorial conclusion
The reason this project has held 3,534 stars is that it refuses to be more than it is. There is no database, no user store and no session handling to attack, because there is no server-side code at all; the container is nginx serving a rollup bundle that talks straight to your registry with the browser's own credentials. What you give up for that simplicity is equally concrete: basic auth only, one registry unless you leave SINGLE_REGISTRY false, HTTPS left to a proxy you run yourself, and image sizes that are compressed rather than extracted. Start with the public demo pointed at a throwaway registry using its url query parameter, read the CORS section before you hit a 401 on a DELETE, and put a reverse proxy in front of it before it holds anything you care about.
Frequently asked questions
What problem does docker-registry-ui actually solve?
It gives a private Docker Registry v2 or v3 instance a browsable web interface, including multi-select tag deletion, sha256 display, numeric-aware sorting and a search bar. Without it you manage a private registry through the Docker CLI, where listing and deleting are awkward at anything above a handful of tags.
Does it support authentication beyond basic auth?
No. The README states that it supports only basic auth and describes itself as a simple standalone frontend that uses your browser window for authentication. There is no user database, no role model and no token issuance, so anyone who can reach the page inherits the permissions of the account you authenticate as.
Why does an image stay in the list after I delete all of its tags?
That is a limitation of the Docker registry rather than the interface: the garbage collector does not remove empty images. The README's answer is to delete the folder in your registry data if you want dangling images gone, and it links the long-running issue for context.
Can I serve it over HTTPS without changes?
Not on its own. The project ships plain nginx listening on port 80 and tells you to put your own reverse proxy in front to handle TLS. Serving the UI over HTTPS while the registry is on plain HTTP is what produces the Mixed Content error described in the FAQ.
What does SINGLE_REGISTRY change?
Set to true and the registry is fixed at one URL with no picker. Set to false and a menu appears in the interface for switching registry URLs dynamically, which is the behaviour the public demo relies on when it is pointed at different registries with the url query parameter.
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/joxit-docker-registry-ui)