{"slug":"container-registry","title":"Container Registry","description":"DanubeData operates a managed OCI registry at cr.danubedata.ro that your CI/CD","section":"Features","url":"https://docs.danubedata.ro/container-registry","markdown_url":"https://docs.danubedata.ro/container-registry.md","breadcrumbs":[{"title":"Features","slug":null},{"title":"Rapids","slug":"serverless-overview"},{"title":"Container Registry","slug":"container-registry"}],"headings":[{"level":1,"title":"Container Registry","id":"container-registry"},{"level":2,"title":"What you get","id":"what-you-get"},{"level":2,"title":"Create an access key","id":"create-an-access-key"},{"level":2,"title":"Log in from Docker","id":"log-in-from-docker"},{"level":2,"title":"Push an image","id":"push-an-image"},{"level":2,"title":"Use the image in a Rapid","id":"use-the-image-in-a-rapid"},{"level":2,"title":"Push from GitHub Actions","id":"push-from-github-actions"},{"level":2,"title":"Push from GitLab CI","id":"push-from-gitlab-ci"},{"level":2,"title":"Push from Bitbucket Pipelines","id":"push-from-bitbucket-pipelines"},{"level":2,"title":"Tag conventions","id":"tag-conventions"},{"level":2,"title":"Delete images","id":"delete-images"},{"level":2,"title":"Plans & limits","id":"plans-limits"},{"level":2,"title":"Troubleshooting","id":"troubleshooting"},{"level":3,"title":"unauthorized: authentication required","id":"unauthorized-authentication-required"},{"level":3,"title":"denied: Storage quota exceeded (X MB used of Y MB). Upgrade your plan or delete tags to free space.","id":"denied-storage-quota-exceeded-x-mb-used-of-y-mb-upgrade-your-plan-or-delete-tags-to-free-space"},{"level":3,"title":"denied: Repository limit reached (X of X). Upgrade your plan or delete an existing repository to add a new one.","id":"denied-repository-limit-reached-x-of-x-upgrade-your-plan-or-delete-an-existing-repository-to-add-a-new-one"},{"level":3,"title":"denied: requested access to the resource is denied","id":"denied-requested-access-to-the-resource-is-denied"},{"level":3,"title":"ImagePullBackOff on a Rapid","id":"imagepullbackoff-on-a-rapid"},{"level":3,"title":"Push hangs at the last layer / \"retrying in N seconds\"","id":"push-hangs-at-the-last-layer-retrying-in-n-seconds"},{"level":2,"title":"Cross-references","id":"cross-references"}],"format":"markdown","word_count":1836,"content":"# Container Registry\n\nDanubeData operates a managed OCI registry at `cr.danubedata.ro` that your CI/CD\ncan push images to and your Rapids can pull from — no third-party credentials\nrequired for your own team's images. Storage is backed by our object-storage\ntier; pulls from inside the platform stay on the cluster LAN (free).\n\n## What you get\n\n- A registry namespace scoped to your team, hosted at\n  `cr.danubedata.ro/{your-team-slug}/...`.\n- **Token-only authentication.** Same flow as DigitalOcean: you generate one\n  token, use it as the Docker password, and put any string (typically your\n  account email) in the username field. We identify the key by hashing the\n  token — the username is informational only.\n- Short-lived (5 min) JWTs under the hood, so a revoked key stops working\n  within minutes.\n- Auto-wired Rapid integration — your team's first-party images appear under\n  \"DanubeData Container Registry\" in the Rapid credential dropdown without any\n  setup.\n- A free Starter plan (1 repository, 500 MB) on every team by default; paid\n  plans for more repositories and storage.\n\nYour team slug is the same `tenant_name` shown in **Team Settings → General**.\nThe rest of this page assumes your team slug is `acme` and your account email\nis `you@example.com`.\n\n## Create an access key\n\n1. Go to **Container Registry** and choose **New access key** at the top of the page.\n2. Enter a **Name** (e.g. `github-actions-prod`).\n3. Pick the **Access**:\n   - **Push and pull** — for CI pipelines that build and publish images.\n   - **Pull only** — for read-only consumers (deploy targets, mirrors).\n4. Click **Create access key**, then [confirm it's you](https://docs.danubedata.ro/account-settings#confirm-its-you) when asked.\n\nWe email you whenever a registry access key is created.\n\nThe **Token** is shown once. Copy or download it now — we never display it\nagain. The token is stored hashed in our database; even root access to the\ndatabase does not expose it.\n\nTokens look like `cr_aBcD12ef...` — the `cr_` prefix lets credential scanners\n(`git-secrets`, `trufflehog`) and humans spot them on sight in `.env` files,\nCI logs, or `~/.docker/config.json`.\n\nYou can create as many keys as you want. To revoke one, open it under\n**Container Registry → Access keys** and choose **Revoke**; the next\n`docker push` against the revoked key fails within 5 minutes. Only the team\nowner creates, sees and revokes access keys.\n\n> **No more \"username\" to copy.** Earlier keys had an auto-generated username\n> like `dd-acme-github-actions-prod-3f9k2a`. New keys still have one for\n> back-compat (visible under the \"Show the auto-generated username\" disclosure\n> in the reveal modal), but you don't need it — any value works in the `-u`\n> field. Your account email is the recommended default.\n\n## Log in from Docker\n\nUse your account email as the username — Docker requires *something* in `-u`,\nand your email is memorable and not secret. The token is the password.\n\n```bash\necho \"$REGISTRY_TOKEN\" | docker login cr.danubedata.ro \\\n  -u you@example.com --password-stdin\n```\n\nFor interactive use:\n\n```bash\ndocker login cr.danubedata.ro -u you@example.com\n# Password: (paste the cr_... token)\n```\n\nAny string works for the username, so this is also valid:\n\n```bash\necho \"$REGISTRY_TOKEN\" | docker login cr.danubedata.ro -u _token --password-stdin\n```\n\n## Push an image\n\nTag the image under your team slug and push:\n\n```bash\ndocker build -t cr.danubedata.ro/acme/hello:1.0.0 .\ndocker push cr.danubedata.ro/acme/hello:1.0.0\n```\n\nThe first path segment **must** be your team slug (`acme` in the example).\nPushes to any other namespace are rejected with `denied: requested access to\nthe resource is denied`.\n\nYou can organise your images however you like under that prefix:\n\n```\ncr.danubedata.ro/acme/web:1.0.0\ncr.danubedata.ro/acme/web:latest\ncr.danubedata.ro/acme/worker:1.0.0\ncr.danubedata.ro/acme/staging/web:abc1234\n```\n\n## Use the image in a Rapid\n\n1. In the Rapid Create or Edit form, set **Deployment type** to **Docker image**.\n2. **Image:** `cr.danubedata.ro/acme/hello:1.0.0`\n3. **Registry credential:** pick **DanubeData Container Registry** from the\n   dropdown — it's pre-seeded for every team and authenticates against your\n   first-party registry.\n4. Save / deploy. Knative pulls the image using a short-lived pull-only\n   credential the platform manages for you.\n\nYou do **not** need to create or rotate any credentials for first-party pulls.\nThe platform handles that.\n\n## Push from GitHub Actions\n\n```yaml\nname: build-and-push\non:\n  push:\n    branches: [main]\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: docker/setup-buildx-action@v3\n      - name: Log in to DanubeData\n        uses: docker/login-action@v3\n        with:\n          registry: cr.danubedata.ro\n          username: ${{ secrets.DD_REGISTRY_USERNAME }}  # your email — not secret\n          password: ${{ secrets.DD_REGISTRY_TOKEN }}      # the cr_... token\n      - name: Build and push\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          push: true\n          tags: |\n            cr.danubedata.ro/acme/hello:${{ github.sha }}\n            cr.danubedata.ro/acme/hello:latest\n```\n\nAdd `DD_REGISTRY_USERNAME` (your account email or any string) and\n`DD_REGISTRY_TOKEN` (the `cr_...` token) under **Settings → Secrets and\nvariables → Actions** in your GitHub repo. Only the token needs to be a\nsecret; the username can also live in plain `vars`.\n\n## Push from GitLab CI\n\n```yaml\nbuild:\n  image: docker:24\n  services:\n    - docker:24-dind\n  stage: build\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  before_script:\n    - echo \"$DD_REGISTRY_TOKEN\" | docker login cr.danubedata.ro -u \"$DD_REGISTRY_USERNAME\" --password-stdin\n  script:\n    - docker build -t cr.danubedata.ro/acme/hello:$CI_COMMIT_SHORT_SHA .\n    - docker push cr.danubedata.ro/acme/hello:$CI_COMMIT_SHORT_SHA\n```\n\nDefine `DD_REGISTRY_USERNAME` (your account email) and `DD_REGISTRY_TOKEN`\n(the `cr_...` token) under **Settings → CI/CD → Variables** (mark the token\nas **Masked** and **Protected**).\n\n## Push from Bitbucket Pipelines\n\n```yaml\nimage: atlassian/default-image:4\npipelines:\n  branches:\n    main:\n      - step:\n          name: Build and push\n          services: [docker]\n          script:\n            - echo \"$DD_REGISTRY_TOKEN\" | docker login cr.danubedata.ro -u \"$DD_REGISTRY_USERNAME\" --password-stdin\n            - docker build -t cr.danubedata.ro/acme/hello:$BITBUCKET_COMMIT .\n            - docker push cr.danubedata.ro/acme/hello:$BITBUCKET_COMMIT\n```\n\nDefine `DD_REGISTRY_USERNAME` (your account email) and `DD_REGISTRY_TOKEN`\n(the `cr_...` token) under **Repository settings → Repository variables**\n(mark the token as **Secured**).\n\n## Tag conventions\n\n- Use **immutable**, sortable tags (commit SHA, semver) for production\n  Rapids — Kubernetes will not re-pull `:latest` once a pod has cached it.\n- `:latest` is fine for development. For production we recommend pinning to\n  a SHA or a semver tag.\n- Tag names must match `[a-z0-9]+(?:[._-][a-z0-9]+)*` per path segment\n  (Distribution's standard rule). Uppercase letters are not allowed in the\n  registry path; they are allowed in tags.\n- We do not currently overwrite-protect tags. Pushing the same tag twice\n  replaces the manifest. Future plans include \"immutable tag\" toggles.\n\n## Delete images\n\nFrom the Container Registry UI:\n\n- **Delete a single image** — open the repository (**Container Registry →\n  Repositories → {repo}**), click the image and choose **Delete** in the panel\n  that opens. The repository stays.\n- **Delete multiple images** — on the repository's page, tick the images you\n  want to remove and click **Delete** above the list. Images a Rapid runs are\n  left out, and the confirmation names them. The repository stays even if all\n  its images are deleted.\n- **Delete the whole repository** — on the repository's page, open **More** and\n  pick **Delete repository**. All its images are removed and the repository\n  drops from your dashboard.\n\nA delete removes the image, not only the tag you chose: other tags that point\nat the same image (say `latest` and `1.4.0`) go with it. The console lists\nthem before you confirm. Deleting is for the team owner.\n\nIn all three cases the underlying blob storage is reclaimed at the next\ngarbage-collection run (daily). Tag and repository deletions are not\nrecoverable.\n\n**Images a Rapid runs are protected.** A tag that one of your serverless\ncontainers was deployed from — or any tag sharing the same image — cannot be\ndeleted from the dashboard, the API or the CLI. Knative pins that exact image\ninto the container's revision, so removing it would break the container's next\ncold start. The refusal names the container: redeploy it on another tag or\nremove it first. When you really mean it, the API accepts `force=true` and the\nCLI `--delete-in-use`.\n\n## Plans & limits\n\n| Plan          | Repositories | Storage | Price       |\n|---------------|--------------|---------|-------------|\n| Starter       | 1            | 500 MB  | Free        |\n| Basic         | 5            | 5 GB    | EUR 3.99/mo |\n| Professional  | unlimited    | 100 GB  | EUR 14.99/mo |\n| Enterprise    | custom       | custom  | Contact us  |\n\nChange plan under **Container Registry → Plan and usage → Change plan**. A\nswitch takes effect immediately and is billed at the new plan's rate from then\non. A downgrade to a plan smaller than what you store goes through, but puts\nyour account on hold: pushes are refused, and so is creating any new resource,\nuntil support lifts the hold. Delete images first to switch without one; the\nconsole says so before you confirm.\n\n## Troubleshooting\n\n### `unauthorized: authentication required`\n\nYour token is wrong, revoked, or expired.\n\n```bash\n# Verify the token directly (username can be anything — try your email):\ncurl -i -u \"you@example.com:$DD_REGISTRY_TOKEN\" \\\n  \"https://danubedata.ro/oci/token?service=cr.danubedata.ro&scope=repository:acme/hello:pull\"\n```\n\nA `200 OK` with a JSON `{\"token\":\"...\", ...}` body means your credentials are\nvalid. A `401` means the token is wrong, revoked, or expired — re-issue from\nthe dashboard.\n\n### `denied: Storage quota exceeded (X MB used of Y MB). Upgrade your plan or delete tags to free space.`\n\nYou are at your plan's storage cap. Either upgrade under **Container\nRegistry → Plan and usage**, or delete unused tags/repositories to reclaim\nquota. The next push succeeds immediately after you free space — actual disk\nbytes are reclaimed at the nightly GC, but the quota check uses your live\n`bytes_used` total which drops as soon as the tag is deleted.\n\n### `denied: Repository limit reached (X of X). Upgrade your plan or delete an existing repository to add a new one.`\n\nYou are trying to push to a **new** repository path while at your plan's\nrepo cap. Either upgrade, or delete an existing repository under\n**Container Registry → Repositories**. Pushing additional tags to a\nrepository you already own is unaffected.\n\n### `denied: requested access to the resource is denied`\n\nThe first path segment of your image does not match your team slug. Tag the\nimage as `cr.danubedata.ro/{your-team-slug}/...` and push again.\n\n### `ImagePullBackOff` on a Rapid\n\nMost common causes, in order:\n\n1. The image tag does not exist (typo, push not yet finished). Check\n   **Container Registry → Repositories → {repo}** to confirm\n   the tag is listed.\n2. The Rapid is configured to pull from a different team's image. First-party\n   pulls only work when the team that owns the Rapid matches the team slug in\n   the image path.\n3. The auto-seeded internal credential is stale (e.g. you renamed the team).\n   Trigger a re-deploy of the Rapid; the platform re-materialises the pull\n   secret.\n\nThe Rapid detail page surfaces the underlying Kubernetes event — it will tell\nyou exactly which of these is in play.\n\n### Push hangs at the last layer / \"retrying in N seconds\"\n\nLikely a transient network issue or you are pushing during the nightly GC\nwindow. Docker retries automatically; if it has not resolved after two\nminutes, contact support.\n\n## Cross-references\n\nIf you need to push from or pull from a **third-party** registry (GHCR,\nDocker Hub, GitLab Container Registry, ECR, etc.) into a Rapid, see\n[Private Container Registry](https://docs.danubedata.ro/serverless-private-registry).\n","prev":{"title":"Rapids Runs","slug":"rapids-runs","url":"https://docs.danubedata.ro/rapids-runs","markdown_url":"https://docs.danubedata.ro/rapids-runs.md","json_url":"https://docs.danubedata.ro/rapids-runs.json"},"next":{"title":"Managed Apps","slug":"managed-apps","url":"https://docs.danubedata.ro/managed-apps","markdown_url":"https://docs.danubedata.ro/managed-apps.md","json_url":"https://docs.danubedata.ro/managed-apps.json"},"index_url":"https://docs.danubedata.ro/index.json"}