Registry API
Base URL: https://registry.cloud.eddisonso.com
The Registry implements the OCI Distribution Specification v1.1. All endpoints are under /v2/.
Authentication
The registry uses the Docker Token Auth flow. Docker handles this automatically when you run docker login.
Login
docker login registry.cloud.eddisonso.com
# Prompts for username and password (same credentials as cloud.eddisonso.com)
Under the hood:
- Docker sends a request to
/v2/— the registry returns401with aWWW-Authenticate: Bearer realm="https://auth.cloud.eddisonso.com/v2/token"header. - Docker exchanges your credentials at
auth.cloud.eddisonso.com/v2/token?service=registry&scope=repository:<name>:<actions>. - Docker uses the returned JWT for subsequent requests.
Manual Token Fetch
TOKEN=$(curl -s \
"https://auth.cloud.eddisonso.com/v2/token?service=registry&scope=repository:myuser/myimage:pull,push,delete" \
-u "myuser:mypassword" | jq -r .token)
Scope Actions
The OCI scope string is repository:<name>:<actions>, where <actions> is a
comma-separated subset of pull, push, and delete. delete is required
for the DELETE manifest and blob endpoints.
Granted actions are filtered by ownership:
- Owner — receives all requested actions.
- Non-owner — no access. All repositories are private; there are no public repositories.
- New (not-yet-existing) repository — full access, so the first
pushcreates it.
Service Account Tokens
For automation, authenticate with a service-account token (ecloud_…) as the
basic-auth password instead of your account password:
echo "$SA_TOKEN" | docker login registry.cloud.eddisonso.com -u myuser --password-stdin
The granted registry actions derive from the service account's
storage.<user-id>.registry scope, mapped to OCI actions:
| Service-account action | OCI action |
|---|---|
read | pull |
create, update | push |
delete | delete |
Manage these scopes on the service account — e.g. with the CLI:
ec auth service-accounts create --name ci --scope storage.me.registry=read,create,delete
Session Tokens
The /v2/ endpoints also accept a session JWT from the auth service (the
same token the dashboard uses) directly in the Authorization: Bearer header,
granting full access to the caller's own repositories. The registry validates
session tokens before OCI registry tokens (both are signed with the same key).
Version Check
GET /v2/
Returns {} with HTTP 200 when authenticated. Returns 401 with a WWW-Authenticate challenge for unauthenticated requests.
Auth: Optional (triggers auth challenge when absent)
curl https://registry.cloud.eddisonso.com/v2/ \
-H "Authorization: Bearer $TOKEN"
# Response: {}
Catalog
GET /v2/_catalog
List repositories owned by the authenticated caller. Returns only the caller's own repositories — unauthenticated callers receive 401. Cross-user catalog browsing is not available.
Auth: Required (returns 401 if absent)
Query params: n (page size), last (pagination cursor)
curl https://registry.cloud.eddisonso.com/v2/_catalog \
-H "Authorization: Bearer $TOKEN"
Response:
{
"repositories": ["myuser/myimage", "myuser/anotherimage"]
}
Paginated:
curl "https://registry.cloud.eddisonso.com/v2/_catalog?n=10&last=myuser/myimage" \
-H "Authorization: Bearer $TOKEN"
Blobs
HEAD /v2/{name}/blobs/{digest}
Check if a blob exists. Returns 200 with Content-Length if found, 404 if not.
Auth: pull on \{name\}
curl -I "https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/sha256:abc123..." \
-H "Authorization: Bearer $TOKEN"
GET /v2/{name}/blobs/{digest}
Download a blob by its content digest.
Auth: pull on \{name\}
curl -o layer.tar.gz \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/sha256:abc123..." \
-H "Authorization: Bearer $TOKEN"
Response: Raw blob bytes with Content-Type: application/octet-stream.
DELETE /v2/{name}/blobs/{digest}
Mark a blob for garbage collection. The blob is not removed from GFS immediately — it is swept on the next GC cycle (every 24 hours).
Auth: delete on \{name\}
curl -X DELETE \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/sha256:abc123..." \
-H "Authorization: Bearer $TOKEN"
# Response: 202 Accepted
POST /v2/{name}/blobs/uploads/
Start a blob upload session. Supports both chunked and monolithic (single-PUT) uploads.
Auth: push on \{name\}
Monolithic upload (include digest query param and full body):
curl -X POST \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/uploads/?digest=sha256:abc123..." \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @layer.tar.gz
# Response: 201 Created, Location: /v2/myuser/myimage/blobs/sha256:abc123...
Chunked upload (no digest, returns upload UUID):
curl -X POST \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/uploads/" \
-H "Authorization: Bearer $TOKEN"
# Response: 202 Accepted
# Location: /v2/myuser/myimage/blobs/uploads/<uuid>
# Range: 0-0
PATCH /v2/{name}/blobs/uploads/{uuid}
Append a chunk to an in-progress upload.
Auth: push on \{name\}
| Param | Type | In | Description |
|---|---|---|---|
| uuid | string | path | Upload session UUID |
| Content-Range | string | header | Optional byte range (e.g. 0-1023) |
curl -X PATCH \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/uploads/<uuid>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @chunk1.bin
# Response: 202 Accepted
# Range: 0-<bytes_received>
PUT /v2/{name}/blobs/uploads/{uuid}
Finalize a chunked upload. Optionally include the last chunk in the body.
Auth: push on \{name\}
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| uuid | string | path | Yes | Upload session UUID |
| digest | string | query | Yes | Expected sha256:... digest of the full blob |
curl -X PUT \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/blobs/uploads/<uuid>?digest=sha256:abc123..." \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream"
# Response: 201 Created
# Location: /v2/myuser/myimage/blobs/sha256:abc123...
Manifests
HEAD /v2/{name}/manifests/{ref}
Check if a manifest exists. \{ref\} can be a tag name or a sha256:... digest.
Auth: pull on \{name\}
curl -I \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/manifests/latest" \
-H "Authorization: Bearer $TOKEN"
GET /v2/{name}/manifests/{ref}
Fetch a manifest by tag or digest.
Auth: pull on \{name\}
curl \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/manifests/latest" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.oci.image.manifest.v1+json"
Response:
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:...",
"size": 1234
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:...",
"size": 5678
}
]
}
PUT /v2/{name}/manifests/{ref}
Push a manifest. Creates a new tag or updates an existing one. All referenced blobs must already be uploaded.
Auth: push on \{name\}
curl -X PUT \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/manifests/v1.0.0" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.oci.image.manifest.v1+json" \
-d @manifest.json
# Response: 201 Created
# Location: /v2/myuser/myimage/manifests/sha256:<digest>
# Docker-Content-Digest: sha256:<digest>
DELETE /v2/{name}/manifests/{ref}
Delete a manifest by tag or digest. Deleting a tag removes only the tag. Deleting a digest removes the manifest record and all tags pointing to it.
Auth: delete on \{name\}
curl -X DELETE \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/manifests/v1.0.0" \
-H "Authorization: Bearer $TOKEN"
# Response: 202 Accepted
Tags
GET /v2/{name}/tags/list
List all tags for a repository.
Auth: pull on \{name\}
Query params: n (page size), last (pagination cursor)
curl \
"https://registry.cloud.eddisonso.com/v2/myuser/myimage/tags/list" \
-H "Authorization: Bearer $TOKEN"
Response:
{
"name": "myuser/myimage",
"tags": ["latest", "v1.0.0", "v1.1.0"]
}
Dashboard API
These endpoints are used by the cloud dashboard (cloud.eddisonso.com) and accept session JWTs issued by the auth service. They are separate from the OCI /v2/ endpoints used by Docker.
Auth: Session JWT in Authorization: Bearer <token> header.
CORS is enabled for cloud.eddisonso.com and *.cloud.eddisonso.com origins.
GET /api/repos
List the authenticated caller's own repositories. Unauthenticated callers receive 401. There are no public repositories — cross-user listing is not available.
Auth: Required (session)
curl https://registry.cloud.eddisonso.com/api/repos \
-H "Authorization: Bearer $SESSION_TOKEN"
Response:
{
"repositories": [
{
"name": "myuser/myapp",
"visibility": 0,
"owner_id": "usr_abc123",
"tag_count": 3,
"total_size": 52428800,
"last_pushed": "2026-03-15T17:00:00Z"
}
]
}
visibility: always 0 (private). All repositories are private.
GET /api/repos/{name}
Get details for a single repository. Owner or a service account scoped to that owner only.
Auth: Session (required)
curl https://registry.cloud.eddisonso.com/api/repos/myuser/myapp \
-H "Authorization: Bearer $SESSION_TOKEN"
GET /api/repos/{name}/tags
List tags for a repository, including digest, size, and push date. Owner or a service account scoped to that owner only.
Auth: Session (required)
curl https://registry.cloud.eddisonso.com/api/repos/myuser/myapp/tags \
-H "Authorization: Bearer $SESSION_TOKEN"
Response:
{
"name": "myuser/myapp",
"tags": [
{
"name": "v1.0.0",
"digest": "sha256:611fec88...",
"size": 25165824,
"pushed_at": "2026-03-15T17:00:00Z"
}
]
}
DELETE /api/repos/{name}/tags/{tag}
Delete a tag. Only the owner may delete tags. If no other tag points to the same manifest, the manifest and unreferenced blobs are scheduled for garbage collection.
Auth: Session (owner only)
curl -X DELETE \
"https://registry.cloud.eddisonso.com/api/repos/myuser/myapp/tags/v1.0.0" \
-H "Authorization: Bearer $SESSION_TOKEN"
Response: 204 No Content
Push/Pull Examples
Push an image
# Tag an existing local image
docker tag myapp:latest registry.cloud.eddisonso.com/myuser/myapp:v1.0.0
# Push
docker push registry.cloud.eddisonso.com/myuser/myapp:v1.0.0
Pull an image
docker pull registry.cloud.eddisonso.com/myuser/myapp:v1.0.0
Use as base image in Dockerfile
FROM registry.cloud.eddisonso.com/myuser/mybase:latest
RUN apt-get install -y mypackage
Pull in a Kubernetes pod
Kubernetes nodes must have the regcred image pull secret configured to pull from the registry:
spec:
imagePullSecrets:
- name: regcred
containers:
- name: myapp
image: registry.cloud.eddisonso.com/myuser/myapp:v1.0.0
Smoke Test Commands
Run these to verify the registry is healthy after deployment:
# 1. Check API version endpoint
curl -s -o /dev/null -w "%{http_code}" \
https://registry.cloud.eddisonso.com/v2/
# Expected: 401 (auth challenge — registry is up)
# 2. Get a token
TOKEN=$(curl -s \
"https://auth.cloud.eddisonso.com/v2/token?service=registry&scope=repository:myuser/test:pull,push" \
-u "myuser:mypassword" | jq -r .token)
# 3. Confirm authenticated access
curl -s -o /dev/null -w "%{http_code}" \
https://registry.cloud.eddisonso.com/v2/ \
-H "Authorization: Bearer $TOKEN"
# Expected: 200
# 4. List catalog
curl -s \
https://registry.cloud.eddisonso.com/v2/_catalog \
-H "Authorization: Bearer $TOKEN"
# 5. Push and pull a test image
docker login registry.cloud.eddisonso.com
docker pull alpine:3.20
docker tag alpine:3.20 registry.cloud.eddisonso.com/myuser/test:smoke
docker push registry.cloud.eddisonso.com/myuser/test:smoke
docker pull registry.cloud.eddisonso.com/myuser/test:smoke