Use the container registry directly
docker login · push · pull against the platform's built-in OCI registry, HTTP registry settings, tenant isolation and the shared namespace
Required permission: View
The platform embeds an OCI container registry (Docker Registry HTTP API V2) in the app. It opens at /v2 on the same address as the app, so docker · containerd · podman push and pull without a separate registry server. Training runtime images, serving images and imported partner images all live here.
Issue a token
- Go to the avatar at the top right → Account settings (계정 설정) → Manage container tokens (컨테이너 토큰 관리). Every member can issue one.
- Set a name and the expiry in days (90 by default; for no expiry choose No expiry (무기한)), then press Issue (발급). ① The token is shown only this once, ② together with a
docker logincommand filled in with this server's host and user name.Right after issuing a container token — ① the secret is shown only now ② a ready docker login command
| Rule | |
|---|---|
pull (GET · HEAD) | The issuer has VIEW or higher in that tenant |
| push (every other request) | The issuer is DEVELOPER · OPERATOR · ADMIN in that tenant |
| Tenant | The tenant at issue time is fixed in the token. The X-Tenant header is not used |
Permissions are checked again on every request. A VIEWER can also get a token, but that token can only pull. If the issuer's role changes later, the change applies immediately to tokens already issued. An ADMIN sees every token of the tenant in the list, other members only their own; a token is revoked by its issuer or an ADMIN.
The API issues tokens too.
curl -sS -X POST "$API/api/v1/registry/tokens" \
-H "Authorization: Bearer $JWT" -H "X-Tenant: DEMO" \
-H 'Content-Type: application/json' -d '{"name": "ci-push", "expires_in_days": 7}'
# -> {"id": "…", "token": "<secret>", "tenant": "DEMO", "username": "demo", "registry_host": "mlops.example.com"}
Login · push · pull
echo "<issued token>" | docker login mlops.example.com -u demo --password-stdin
docker tag example/mnist-trainer:v1 mlops.example.com/demo/example/mnist-trainer:v1
docker push mlops.example.com/demo/example/mnist-trainer:v1
docker pull mlops.example.com/demo/example/mnist-trainer:v1
- Any user name works. The token is the password. For readability, the tenant code (lower case) is recommended.
- Do not put a scheme such as
http://in an image address. It is alwayshost[:port]/path:tag. - Storage is fully separated per tenant. The first path segment is just a name; whatever name you push under, the image goes into the token's tenant space. By convention, putting the tenant code in the first segment (
demo/…) gives the same shape as images imported on screen. - Another tenant's images look as if they do not exist (
not found). The registry deliberately answers 404 so that "exists but no permission" cannot be told apart from "does not exist".
The standard API lists repositories and tags.
curl -u "demo:<token>" https://mlops.example.com/v2/_catalog
curl -u "demo:<token>" https://mlops.example.com/v2/demo/example/mnist-trainer/tags/list
# {"name":"demo/example/mnist-trainer","tags":["v1"]}
_catalog lists only your own tenant's repositories. Shared mlops/… images granted to you are not listed, but they pull if you know the tag.
Delete images
/v2 has no delete request, so docker cannot delete images. Delete them in the container registry screen, or call the REST API with the DevelopDEVELOPER · OPERATOR · ADMIN permission.
curl -sS -X DELETE -H "Authorization: Bearer $JWT" -H "X-Tenant: DEMO" \
"$API/api/v1/registry/repositories/demo/example/mnist-trainer/tags/v1"
# {"name":"demo/example/mnist-trainer","tag":"v1","repository_deleted":true}
- Deleting the last tag removes the repository too. To delete a whole repository, use
DELETE /api/v1/registry/repositories/{name}. - If the platform uses the image (a training runtime variant, a model image, a deployment, serving and so on), the answer is
409, with the users listed indetail.references. - Shared
mlops/…images cannot be deleted.
A registry open over HTTP (insecure registry)
If the registry is behind HTTPS, there is nothing to configure. If the platform is open only over HTTP, you must configure the client side to allow HTTP. Docker allows localhost as an exception, so it works without configuration.
Add the host to /etc/docker/daemon.json and restart Docker.
{ "insecure-registries": ["mlops.example.com:10000"] }sudo systemctl restart dockerpodman login --tls-verify=false mlops.example.com:10000 -u demo
podman push --tls-verify=false mlops.example.com:10000/demo/example/mnist-trainer:v1Configure this on the nodes where training and serving pods pull images. /etc/rancher/k3s/registries.yaml:
mirrors:
"mlops.example.com:10000":
endpoint: ["http://mlops.example.com:10000"]
configs:
"mlops.example.com:10000":
tls:
insecure_skip_verify: trueAfter saving, restart k3s (sudo systemctl restart k3s; on agent nodes, k3s-agent).
The operator usually sets up nodes at installation. The procedure is in Installation.
The shared namespace mlops
Repositories whose path starts with mlops/ are in a reserved shared namespace. A system administrator uses it to build training runtime · dataset stager · serving builder images once and share them with several tenants.
| Tenant token | Platform token (system administrator) | |
|---|---|---|
mlops/… pull | Only tags shared with the tenant. Tags and repositories not shared return not found | All |
mlops/… push | Rejected (shared namespace is read-only for tenant tokens) | Allowed |
| Own tenant space | Allowed | None |
So sharing is per tag. Even within the same mlops/training-runtime repository, tenant A may get only the yolo-cuda tag and tenant B only the rfdetr-cpu tag. The system administrator decides who gets which shared images (Administration guide).
You cannot put images into mlops/ by import (tar upload). An import always goes into your own tenant space.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
http: server gave HTTP response to HTTPS client | An HTTP registry, but the client tried HTTPS. Apply the insecure setting above |
unauthorized / 401 | The token is wrong, expired or revoked. Issue a new one |
push fails with 403 insufficient permission | The token's issuer lacks the DevelopDEVELOPER · OPERATOR · ADMIN permission in that tenant (VIEWER · APPROVER) |
push fails with denied: shared namespace is read-only for tenant tokens | The path starts with mlops/. Use a different first segment |
A layer push fails with 413 BLOB_UPLOAD_INVALID | One layer exceeds the server limit (10 GiB by default, GEO_MLOPS_REGISTRY_MAX_BLOB_BYTES) |
pull returns not found | Another tenant's image, an mlops/ tag not shared with you, or it really does not exist |
| A layer push is cut off midway | The body size or time limit of a front proxy. Tell the operator, or hand it over as a file and import it on screen |