Skip to content

Working with OCI / Docker

CodeScoring.Save implements OCI Distribution Spec under the /v2/ prefix and supports Docker, Helm OCI charts, and other OCI artifacts such as oras.

Proxy Repository

curl -X POST https://save.example.com/api/v1/repos \
  -H "Content-Type: application/json" \
  -u "<username>:<password>" \
  -d '{
    "project": "common",
    "name": "dockerhub-proxy",
    "format": "docker",
    "repository_type": "proxy",
    "remote_url": "https://registry-1.docker.io",
    "cache_ttl": 86400
  }'

For single-name images without slashes, standard Docker Hub normalization library/<image> is applied.

Hosted Repository

curl -X POST https://save.example.com/api/v1/repos \
  -H "Content-Type: application/json" \
  -u "<username>:<password>" \
  -d '{
    "project": "common",
    "name": "docker-hosted",
    "format": "docker",
    "repository_type": "hosted"
  }'

URL Scheme

The base scheme is path-based, as required by Docker / OCI:

<host>/<project>/<repository>/<image>:<tag>

Nexus-compatible flat URL routing (flat-alias) is also supported. For compatible repositories, the <project>/<repository> prefix is replaced with a short alias or omitted entirely.

Client Configuration

Docker / Podman

# Login
docker login save.example.com

# Pull through proxy
docker pull save.example.com/common/dockerhub-proxy/library/nginx:latest

# Push to hosted
docker tag myapp:latest save.example.com/common/docker-hosted/myapp:1.0.0
docker push save.example.com/common/docker-hosted/myapp:1.0.0

Anonymous read and docker login

Endpoint /v2/ always returns 401 with a WWW-Authenticate: Bearer realm=... challenge header, even if the repository allows anonymous read. This is required so the Docker client correctly performs the 401 -> retry -> 200 cycle and then sends Basic Auth if needed. This is an OCI Distribution Spec requirement.

Robot accounts in CI

For CI/CD, use a robot account: docker login -u 'sa$<robot-name>' -p '<api-key>' save.example.com. The same approach works for helm registry login and oras login. For details, see Authentication.

Helm

CodeScoring.Save accepts Helm charts as regular OCI artifacts. Push/pull is performed with standard helm commands:

# Login (Helm 3.8+)
helm registry login save.example.com -u <username>

# Push
helm package ./mychart
helm push mychart-0.1.0.tgz oci://save.example.com/common/docker-hosted

# Pull
helm pull oci://save.example.com/common/docker-hosted/mychart --version 0.1.0

Plain HTTP for test stands

For test stands without TLS, use --plain-http (Helm 3.13+):

helm push mychart-0.1.0.tgz oci://save.example.com/common/docker-hosted --plain-http

oras

oras works with any OCI-compatible artifact: configs, policies, SBOMs, binaries, and others.

# Push an arbitrary file as an OCI artifact
oras push save.example.com/common/docker-hosted/configs/app:v1 \
  -u <username> -p <password> \
  ./config.yaml:application/vnd.example.config

# Pull
oras pull save.example.com/common/docker-hosted/configs/app:v1 \
  -u <username> -p <password>

containerd / nerdctl

nerdctl login save.example.com
nerdctl pull save.example.com/common/dockerhub-proxy/library/alpine:latest

Flat URL Routing

Save supports Nexus-compatible flat namespace for migration from Nexus / Artifactory: a repository can be configured with a prefix alias so that it responds to requests without <project>/<repository> in the path.

Longest-prefix matching is used:

  • docker pull save.example.com/common/external/golang:1.24.1 goes to the repository with flat_alias = common/external/.
  • docker pull save.example.com/johnny-depp:v1 goes to a catch-all hosted repository if it is configured.

When creating a repository with a flat alias, use flat_alias_prefix and/or is_catch_all. Prefixes are validated for uniqueness.

Repository URL Migration

Use case: migrating a Docker registry from Nexus / Artifactory to CodeScoring.Save.

Source URL before migration URL after migration (with project) URL after migration (flat-alias)
Nexus nexus.host.ru:5000/library/nginx:latest save.example.com/common/dockerhub-proxy/library/nginx:latest save.example.com/library/nginx:latest
Artifactory jfrog.host.ru/docker-remote/library/nginx save.example.com/common/dockerhub-proxy/library/nginx save.example.com/library/nginx
Docker Hub docker.io/library/postgres:16 save.example.com/common/dockerhub-proxy/library/postgres:16 save.example.com/library/postgres:16

The flat-alias option does not require client-side changes: existing scripts with a hardcoded nexus.host.ru/library/nginx path start working after the host is replaced.

Troubleshooting

Checking /v2/

curl -i https://save.example.com/v2/
# Expected: HTTP/1.1 401 Unauthorized with Www-Authenticate: Bearer realm=...

Checking the Token Endpoint

curl -u "<username>:<password>" \
  "https://save.example.com/v2/token?service=save.example.com&scope=repository:common/dockerhub-proxy/library/nginx:pull"

The response is JSON with the token field containing a short-lived Docker v2 JWT.

Listing Tags

curl -u "<username>:<password>" \
  https://save.example.com/v2/<project>/<repository>/<image>/tags/list

Service Status

curl https://save.example.com/health

Repository Audit

curl -u "<username>:<password>" \
  "https://save.example.com/api/v1/admin/audit?resource_type=repository&q=dockerhub-proxy&limit=50"
Страница была полезна?