Authentication

Opt-in. Without AUTH_ENABLED, nothing changes: no login page, no session, no auth code loaded.

Dashboard with authentication off Auth off = no Audit Log in the sidebar. There is no actor to record.

Which mode?

You haveUseNotes
No identity systemNone (default)Nothing to configure
A few named peopleLocalStatic list, no directory
Active Directory, OpenLDAPLDAP / ADAdmin comes from a group
Entra ID, Keycloak, Okta, Authentik, GoogleOIDC / SSOCredentials never reach the UI
Always add a recovery account. See Locked out.

Use cases

Try it locally

One command. Ships OpenLDAP, a registry and the UI.

git clone https://github.com/VibhuviOiO/docker-registry-ui
cd docker-registry-ui/docker/ldap-auth
docker compose up -d --build
http://localhost:5006 — alice/alicepassword (admin), bob/bobpassword (viewer). Stop: docker compose down

Sign in with an identity provider

docker run -d --name registry-ui -p 5000:5000 \
  -v "$PWD/data:/app/data" \
  -e AUTH_ENABLED=true \
  -e AUTH_MODE=oidc \
  -e AUTH_SECRET_KEY="$(openssl rand -base64 32)" \
  -e AUTH_PUBLIC_URL=https://registry.example.com \
  -e OIDC_ISSUER=https://login.microsoftonline.com/<tenant>/v2.0 \
  -e OIDC_CLIENT_ID=<client-id> \
  -e OIDC_CLIENT_SECRET=<client-secret> \
  -e OIDC_ADMIN_GROUP=registry-admins \
  -e REGISTRIES='[{"name":"Mine","api":"https://registry.example.com"}]' \
  vibhuvioio/docker-registry-ui:latest

Register this redirect URI with the provider:

<AUTH_PUBLIC_URL>/auth/oidc/callback

SSO sign-in page

Only a few people may delete

-e LDAP_ADMIN_GROUP='registry-admins;platform-admins'

Rule
Separator;
Not, — that is part of a DN
OIDC equivalentOIDC_ADMIN_GROUP

Locked out

Checked before any network call. Works while the provider is down.

-e BREAK_GLASS_USER=emergency -e BREAK_GLASS_PASSWORD='<long random value>'
Store it in a secret manager, not in a committed compose file.

Roles

RoleCan
viewerBrowse repositories, tags, analytics, scan results
adminAbove, plus delete, bulk operations, registry config, audit log
AUTH_DEFAULT_ROLE defaults to viewer. READ_ONLY=true overrides both.

Local login

docker run -d --name registry-ui -p 5000:5000 \
  -v "$PWD/data:/app/data" \
  -e AUTH_ENABLED=true \
  -e AUTH_MODE=local \
  -e AUTH_SECRET_KEY="$(openssl rand -base64 32)" \
  -e APPLICATION_USERS='[{"username":"alice","password":"alicepassword","role":"admin","displayName":"Alice Example"}]' \
  -e REGISTRIES='[{"name":"Mine","api":"https://registry.example.com"}]' \
  vibhuvioio/docker-registry-ui:latest

LDAP / AD

LDAP sign-in page

The bind account needs read access only.

docker run -d --name registry-ui -p 5000:5000 \
  -v "$PWD/data:/app/data" \
  -e AUTH_ENABLED=true \
  -e AUTH_MODE=ldap \
  -e AUTH_SECRET_KEY="$(openssl rand -base64 32)" \
  -e LDAP_URL=ldaps://ldap.example.com:636 \
  -e LDAP_BASE_DN=dc=example,dc=com \
  -e LDAP_BIND_DN='cn=registry-ui,ou=Services,dc=example,dc=com' \
  -e LDAP_BIND_PASSWORD_FILE=/run/secrets/ldap_bind_password \
  -e LDAP_ADMIN_GROUP=registry-admins \
  -e REGISTRIES='[{"name":"Mine","api":"https://registry.example.com"}]' \
  vibhuvioio/docker-registry-ui:latest
VariablePurpose
LDAP_URLldap://host:389 or ldaps://host:636
LDAP_BASE_DNSearch root
LDAP_BIND_DN / LDAP_BIND_PASSWORDService account. ..._PASSWORD_FILE for Docker secrets
LDAP_USER_FILTERDefault matches uid. Use sAMAccountName for AD
LDAP_ADMIN_GROUPMembers become admin
LDAP_STARTTLStrue upgrades a plain connection
LDAP_CA_CERTCA bundle for a private CA
LDAP_INSECUREtrue skips verification. Testing only
Working example: docker/ldap-auth/docker-compose.yml

OIDC / SSO

VariablePurpose
OIDC_ISSUERProvider base URL. Discovery read from /.well-known/openid-configuration
OIDC_CLIENT_ID / OIDC_CLIENT_SECRETFrom the provider
AUTH_PUBLIC_URLBrowser-facing URL. The callback is built from it
OIDC_ADMIN_GROUPClaim value that grants admin
OIDC_USERNAME_CLAIMDefault preferred_username
OIDC_GROUPS_CLAIMDefault groups
OIDC_CA_CERTCA bundle for an internal provider
Rule
FlowAuthorization code + PKCE + nonce
TokensRS256, verified against the provider JWKS
AUTH_PUBLIC_URLThe browser's address, not the container's
Admin group set but claim missingEveryone becomes viewer. The log has a warning

Audit log

Every sign-in, failure, sign-out, deletion and denied privileged action lands in audit.log inside DATA_DIR.

Field
Actor, roleWho
Action, resultWhat
Client IP, timestampWhere and when
Admins read it under Audit Log. Viewers get 403.

Append-only, locked, rotates at 5 MB. Mount it to keep it:

-v "$PWD/data:/app/data"

Multiple workers

RuleWhy
Same AUTH_SECRET_KEY in every workerOtherwise users are signed out at random
One shared DATA_DIRAudit log and configuration live there

Verify

B=http://localhost:5000

curl -si $B/api/registries | head -1        # 401 without a session

curl -s -c jar.txt -o /dev/null -X POST $B/auth/login \
  --data-urlencode username=alice --data-urlencode password=alicepassword
curl -s -b jar.txt $B/auth/me

All variables: app/auth/config.py

Troubleshooting

SymptomCause
503 on sign-inDirectory unreachable. Check LDAP_URL and TLS
401 for every userWrong bind DN/password, or the filter matches nothing
Always viewerNo groups returned. Set LDAP_GROUP_FILTER, or check OIDC_GROUPS_CLAIM
Random sign-outsAUTH_SECRET_KEY differs between workers
Config lost on restartDATA_DIR not mounted
Empty passwordRejected before any bind. Some servers would treat it as anonymous