Self-hosted OCI container registry with managed access, SQLite, filesystem storage, and admin UI.
345
Start the registry with the published Docker image:
docker run --rm --name scr \
-p 5000:5000 \
-v scr-data:/var/lib/scr \
-e SCR_BOOTSTRAP_ADMIN_USERNAME=admin \
-e SCR_BOOTSTRAP_ADMIN_PASSWORD=change-me \
bytebardorg/simplecontainerregistry:latest
The container listens on port 5000, uses /etc/scr/config.yaml, and stores registry content plus SQLite state under /var/lib/scr. The named volume keeps data across container restarts.
Open the admin UI:
http://localhost:5000/ui
Sign in with the bootstrap admin username and password. The bootstrap admin is created only if that username does not already exist.
Log in with a Docker-compatible client and push an image:
printf '%s\n' 'change-me' | docker login localhost:5000 -u admin --password-stdin
docker pull busybox:latest
docker tag busybox:latest localhost:5000/getting-started/busybox:latest
docker push localhost:5000/getting-started/busybox:latest
docker pull localhost:5000/getting-started/busybox:latest
For non-local deployments, use a strong bootstrap password and run SCR behind TLS, usually through a reverse proxy.
The published Docker image starts SCR with -config /etc/scr/config.yaml. The included file is equivalent to:
http:
address: "0.0.0.0"
port: 5000
secureCookies: true
storage:
rootDirectory: "/var/lib/scr/registry"
gc: true
gcDelay: "1h"
gcInterval: "24h"
database:
driver: "sqlite"
dsn: "/var/lib/scr/scr.db"
auth:
issuer: "scr"
service: "scr"
tokenTTL: "10m"
To use a custom configuration file, mount it over /etc/scr/config.yaml:
docker run --rm --name scr \
-p 5000:5000 \
-v scr-data:/var/lib/scr \
-v "$PWD/config.yaml:/etc/scr/config.yaml:ro" \
-e SCR_BOOTSTRAP_ADMIN_USERNAME=admin \
-e SCR_BOOTSTRAP_ADMIN_PASSWORD=change-me \
bytebardorg/simplecontainerregistry:latest
Configuration supports these sections:
http.address and http.porthttp.secureCookies; defaults to true. Leave enabled when SCR is accessed over HTTPS, including behind an HTTPS-terminating reverse proxy. Set to false only when serving the admin UI directly over plain HTTP.storage.rootDirectorystorage.gcstorage.gcDelaystorage.gcIntervaldatabase.driver set to sqlite; other database drivers are not supporteddatabase.dsnauth.issuerauth.serviceauth.tokenTTLbootstrap.adminUsernamebootstrap.adminPasswordDefault container paths:
/etc/scr/config.yaml/var/lib/scr/var/lib/scr/registry/var/lib/scr/scr.db5000Bootstrap admin username and password are normally provided with environment variables:
SCR_BOOTSTRAP_ADMIN_USERNAMESCR_BOOTSTRAP_ADMIN_PASSWORDIf bootstrap admin values are omitted from the config file, SCR fills them from those environment variables. Provide both values together.
SCR stores admin UI sessions in an HttpOnly, SameSite=Lax cookie. By default, http.secureCookies is true, which also marks that cookie Secure so browsers only send it over HTTPS.
Keep http.secureCookies: true for production deployments, including the common setup where a reverse proxy terminates HTTPS and forwards plain HTTP to SCR. The browser only sees the public HTTPS URL, so the Secure cookie works normally even if the proxy-to-SCR hop is HTTP.
Set http.secureCookies: false only when users access SCR directly over plain HTTP, such as a local development instance or a trusted internal HTTP-only deployment. Do not disable it for an HTTPS reverse-proxy deployment.
Registry clients use Docker-compatible bearer-token authentication:
GET /token?service=scr&scope=repository:{name}:pull,push with HTTP Basic auth./v2/... endpoints.WWW-Authenticate bearer challenges when auth is missing or insufficient.Admin API routes require an admin bearer token from /token.
Access model:
* for all repositories or a simple repository string prefix such as shieldedstack/.team/ matches repositories under that namespace, while team/app also matches names beginning with team/app.Security and storage behavior:
Admins can configure a registry webhook URL from /ui/settings. Leave the URL empty to disable delivery.
SCR sends registry webhooks as best-effort HTTP POST requests with Content-Type: application/json and User-Agent: simplecontainerregistry-webhook. Webhook delivery is asynchronous, has a short timeout, and does not fail the original registry or admin UI request if the destination is slow, unavailable, or returns a non-2xx response.
Delivered events:
registry.manifest.pulled with group registry.pullregistry.blob.pulled with group registry.pullregistry.manifest.pushed with group registry.pushregistry.blob.pushed with group registry.pushregistry.manifest.deleted with group registry.deleteregistry.blob.deleted with group registry.deleterepository.deleted with group registry.delete when an admin deletes a repository from the UIWebhook payload schema:
{
"id": "aud_...",
"event": "registry.manifest.pushed",
"group": "registry.push",
"targetType": "repository",
"targetId": "team/app",
"actorUserId": "usr_...",
"result": "success",
"ipAddress": "203.0.113.10",
"userAgent": "docker/27.0.0 go/go1.22 git-commit/... kernel/... os/linux arch/amd64 UpstreamClient(Docker-Client/27.0.0)",
"createdAt": "2026-07-11T12:34:56Z"
}
Payload fields:
id: stable audit event ID for this webhook event.event: exact event name.group: coarse event group, one of registry.pull, registry.push, or registry.delete.targetType: event target type. Registry webhook events currently use repository.targetId: repository name, such as team/app.actorUserId: authenticated user ID when available. This field is omitted for anonymous/system events.result: audit result. Registry webhook events currently emit successful events only.ipAddress: client IP resolved from X-Forwarded-For, X-Real-IP, or the remote address.userAgent: request user agent.createdAt: event creation timestamp in RFC 3339 format.OCI clients can make multiple registry API calls for one high-level image operation. For example, a single docker pull usually emits one manifest pull event plus one or more blob pull events.
Registry access denials are stored in audit events as registry.access.denied with result denied. They are not delivered to registry webhooks.
OCI Distribution API:
GET /v2/GET /v2/_catalogPOST /v2/{name}/blobs/uploads/PATCH /v2/{name}/blobs/uploads/{upload_id}GET /v2/{name}/blobs/uploads/{upload_id}PUT /v2/{name}/blobs/uploads/{upload_id}?digest={digest}GET /v2/{name}/blobs/{digest}HEAD /v2/{name}/blobs/{digest}DELETE /v2/{name}/blobs/{digest}PUT /v2/{name}/manifests/{reference} including digest references and ?tag={tag} query parametersGET /v2/{name}/manifests/{reference}HEAD /v2/{name}/manifests/{reference}DELETE /v2/{name}/manifests/{reference}GET /v2/{name}/tags/list including n and last paginationGET /v2/{name}/referrers/{digest} including artifactType filteringImplemented OCI Distribution behavior includes sha256 and sha512 digests, blob range requests, monolithic and chunked blob uploads, upload status checks, digest-validated manifest pushes, OCI referrers, OCI-Subject and OCI-Tag response headers, manifest delete, blob delete, tag delete, and catalog/tag pagination.
Authentication and health:
GET /healthzGET /tokenAdmin API:
GET /api/usersPOST /api/usersGET /api/users/{id}DELETE /api/users/{id}POST /api/users/{id}/disablePOST /api/users/{id}/enableGET /api/grantsPOST /api/grantsDELETE /api/grants/{id}GET /api/dashboard/summaryGET /api/repositoriesGET /api/repositories/{name}GET /api/repositories/{name}/tagsGET /api/audit-eventsAdmin UI:
GET /uiGET /ui/loginPOST /ui/loginPOST /ui/logoutGET /ui/repositoriesPOST /ui/repositories/deletePOST /ui/repositories/delete-tagGET /ui/usersPOST /ui/usersPOST /ui/users/{id}/accessPOST /ui/users/{id}/deleteGET /ui/auditGET /ui/settingsPOST /ui/settings/gcPOST /ui/settings/webhookGET / redirects to /v2/ so container clients see the registry API root by default.
Content type
Image
Digest
sha256:be08c6415…
Size
6.3 MB
Last updated
about 1 month ago
docker pull bytebardorg/simplecontainerregistry