Container images (Compute API)
Registering an image saves its URL under your account, and — for a private
registry — stores the credentials we pull it with. You launch by passing the
same URL as container_image; the credential is found and used automatically.
A public image needs none of this. Pass its URL straight to
POST /gpu-instances and skip this page.
Before you start
Compute access on the account and a key with Compute access — see the Compute API overview.
https://api.ecohash.com
Authorization: Bearer eco_YOUR_KEY
What a credential is
Every registry that speaks the Docker Registry v2 protocol authenticates with a username and a secret. A token is not a different kind of credential — it is what goes in the secret field.
| Registry | Username | Password or access token |
|---|---|---|
| Docker Hub | your account name | a personal access token |
| GitHub Container Registry (ghcr.io) | your GitHub username | a classic PAT with read:packages |
| GitLab registry | a deploy-token username | that deploy token |
| Quay.io | robot account name (org+robot) | the robot's token |
| Harbor and most self-hosted | robot account name | the robot's secret |
| Azure Container Registry | service principal app id | its password (or the admin user) |
| Google Artifact Registry | _json_key | the service-account JSON key, as one line |
AWS ECR is not supported, and registering a credential for it is refused
rather than accepted and left to break. aws ecr get-login-password mints a
token that expires after 12 hours, so a stored one stops working within a day —
long after you would connect the failure to this step. Two things do work:
make the repository public, or grant our account pull access in the
repository's policy and register the image with no credential at all.
Register a private image
curl -s -X POST https://api.ecohash.com/container-images \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"name": "trainer",
"registry_url": "ghcr.io/acme/trainer:v3",
"registry_username": "acme-bot",
"registry_secret": "ghp_xxxxxxxxxxxxxxxxxxxx"
}'
{
"id": 42,
"name": "trainer",
"registry_url": "ghcr.io/acme/trainer:v3",
"registry_username": "acme-bot",
"has_credentials": true,
"created_at": "2026-09-24T09:12:03Z"
}
has_credentials is how you confirm the secret landed. The secret is never
returned by any endpoint — there is no way to read it back, and a lost
credential is re-entered rather than recovered.
Launch from it
Nothing special: pass the same registry_url as container_image.
curl -s -X POST https://api.ecohash.com/gpu-instances \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"name": "train-run",
"region_id": "atl",
"gpu_type": "NVIDIA-RTX-PRO-6000-Blackwell-Server-Edition",
"gpu_count": 1,
"container_image": "ghcr.io/acme/trainer:v3",
"estimated_duration_hours": 0
}'
The match is on the exact registry_url string. ghcr.io/acme/trainer:v3
and ghcr.io/acme/trainer:v4 are two different images: register each tag you
launch, or register the tag you actually use.
If the stored credential is wrong, the launch is refused with 400 before
anything is created — no instance, no credit hold:
{"error":"the stored credential for ghcr.io/acme/trainer:v3 was rejected by ghcr.io — check the username and token on the image in Registry → Images"}
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /container-images | Your registered images |
GET | /container-images/{id} | One image |
POST | /container-images | Register one |
PUT | /container-images/{id} | Change name, URL or credentials |
DELETE | /container-images/{id} | Remove the registration (and its credential) |
Fields
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | A label for your own use |
registry_url | string | yes | The full pullable reference including tag |
registry_username | string | no | Required together with registry_secret |
registry_secret | string | no | Password or access token. Write-only |
registry_username and registry_secret must be sent together. Half a
credential authenticates nothing, so sending one without the other is a 400
rather than a registration that silently cannot pull.
Responses
| Field | Meaning |
|---|---|
id, name, registry_url, created_at | |
registry_username | Present when a credential is stored. Not a secret — it is the identity the pull uses |
has_credentials | true when a secret is stored |
Update or remove a credential
Change it by sending a new pair:
curl -s -X PUT https://api.ecohash.com/container-images/42 \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"registry_username": "acme-bot", "registry_secret": "ghp_new_token"}'
Remove it — the image stays registered and pulls anonymously afterwards:
curl -s -X PUT https://api.ecohash.com/container-images/42 \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"registry_username": "", "registry_secret": ""}'
Omitting both fields leaves whatever is stored untouched, so you can rename an image without re-entering its token.
How the credential is handled
- Stored encrypted, under a key held only by the API. It is never written to a log, never returned by an endpoint, and never sent to your pod.
- Used at launch to create a pull secret in your account's namespace in the
region you launched into. It is scoped to the registry it belongs to, so a
credential for
ghcr.iois never offered to any other registry. - Deleted with the image registration.
Rotating a token
Registries expire tokens. When yours changes, PUT the new one — running
instances are unaffected, because the image was already pulled. The new
credential applies to the next launch.
A launch that fails on a rotated-out token gives you the 400 above rather than
a failed instance, so the fix is visible at the moment you hit it.
Related
GPU instances · Container images in the console · Compute API overview