Skip to content

Docker Repositories

View as Markdown

This page covers using the Docker CLI with CloudRepo private Docker repositories.

Push, pull and build with Docker against a CloudRepo Docker repository. CloudRepo speaks the Docker Registry HTTP API V2, so the Docker CLI needs nothing beyond docker login.

  • A Docker repository. If you have none, see Creating a Repository.
  • A repository token that reaches it, with Read + write if you will push. See Repository Tokens: Create One and Authenticate. Its format is the Generic one, which starts with crp_v1_. An npm-format token (crn_v1_) does not authenticate here. The password is the token, and the username is not checked: use your email address, as every other client does.
  • The Connection Settings of the repository show its image reference and ready-made CI snippets with your names filled in.

1. Log in to the host. Pipe the token to docker login on standard input:

Terminal
echo "$CLOUDREPO_TOKEN" | docker login your-org.mycloudrepo.io \
--username you@example.com \
--password-stdin

Expected: Login Succeeded. To be prompted instead, run docker login your-org.mycloudrepo.io and enter your email address and the token.

2. Build and push. The image reference carries the repository: the host, the literal repositories/ segment, your repository, then the image and tag.

Terminal
docker build -t your-org.mycloudrepo.io/repositories/your-repo/my-app:1.0.0 .
docker push your-org.mycloudrepo.io/repositories/your-repo/my-app:1.0.0

To push an image you already have, tag it with that reference first:

Terminal
docker tag my-app:1.0.0 your-org.mycloudrepo.io/repositories/your-repo/my-app:1.0.0

3. Pull. The login you made applies. You logged in to the bare host, but you pull the /repositories/ path:

Terminal
docker pull your-org.mycloudrepo.io/repositories/your-repo/my-app:1.0.0

For GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite and the like, keep the token in the platform’s secret store, expose it as the environment variable CLOUDREPO_TOKEN, and use the same login as above:

Terminal
echo "$CLOUDREPO_TOKEN" | docker login your-org.mycloudrepo.io \
--username you@example.com \
--password-stdin

--password-stdin is the Docker CLI’s option for reading the password from standard input instead of the command line. Never commit the token to a repository. The variable name is yours to choose: the admin portal’s ready-made GitHub Actions and GitLab CI snippets call it CLOUDREPO_PASSWORD, and the value is the same repository token either way.

Install packages from CloudRepo in a Docker build

Section titled “Install packages from CloudRepo in a Docker build”

When a docker build installs your private Maven, Python or npm packages from CloudRepo, the step that downloads them needs your repository token. Give it to that step as a build secret, never as a build argument: Docker records build arguments in the image’s history, so the token would travel with the image. Docker’s documentation advises against build arguments for credentials. See Build secrets.

Pass the username the same way. It is the email address of the account that created the token. Both values come from environment variables, so neither appears in the command:

Terminal
docker build \
--secret id=cloudrepo_user,env=CLOUDREPO_USERNAME \
--secret id=cloudrepo,env=CLOUDREPO_TOKEN \
-t my-app .

In the Dockerfile, mount the secrets on the RUN step that downloads packages. env=CLOUDREPO_TOKEN sets that variable for that one step, and the client reads it from there. Keep the first line, # syntax=docker/dockerfile:1: mounting a secret as a variable needs Dockerfile syntax 1.10 or later, and that line fetches it for any Docker with BuildKit (the default builder since Docker Engine 23).

Do not write the token into a file in that step: a file a RUN step leaves behind is part of the image. The examples below keep the token in the variable and write only the variable’s name to disk.

If the variable is not set when you run docker build, Docker passes an empty secret rather than failing, and the download step is refused as if the token were wrong.

Maven. A multi-stage build is the norm: the build stage has Maven and the JDK, and the runtime stage gets only the jar. settings.xml names the variables, never their values, and Maven reads ${env.NAME} when it runs. The <id> must match the repository <id> in your pom.xml (Repository Tokens: Create One and Authenticate shows both files).

settings.xml (next to the Dockerfile)
<settings>
<servers>
<server>
<id>cloudrepo</id>
<username>${env.CLOUDREPO_USERNAME}</username>
<password>${env.CLOUDREPO_TOKEN}</password>
</server>
</servers>
</settings>
Dockerfile - Maven
# syntax=docker/dockerfile:1
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY settings.xml /root/.m2/settings.xml
COPY pom.xml .
COPY src ./src
RUN --mount=type=secret,id=cloudrepo_user,env=CLOUDREPO_USERNAME \
--mount=type=secret,id=cloudrepo,env=CLOUDREPO_TOKEN \
mvn -B package -DskipTests
FROM eclipse-temurin:17-jre
COPY --from=build /app/target/*.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

pip. pip takes the credential inside the index URL. Set PIP_INDEX_URL for pip in the RUN step itself, so the URL exists only for that command. CLOUDREPO_USERNAME is the plain email address: pip splits the credential from the host at the last @, so the one in the email needs no %40 here.

Dockerfile - pip
# syntax=docker/dockerfile:1
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=secret,id=cloudrepo_user,env=CLOUDREPO_USERNAME \
--mount=type=secret,id=cloudrepo,env=CLOUDREPO_TOKEN \
PIP_INDEX_URL="https://${CLOUDREPO_USERNAME}:${CLOUDREPO_TOKEN}@your-org.mycloudrepo.io/repositories/your-repo/simple/" \
pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

npm. npm sends the token on its own, so it needs only the cloudrepo secret. The project’s .npmrc names the variable, and npm reads it when it runs:

.npmrc
registry=https://your-org.mycloudrepo.io/repositories/your-repo/
//your-org.mycloudrepo.io/repositories/your-repo/:_authToken=${CLOUDREPO_TOKEN}
Dockerfile - npm
# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json .npmrc ./
RUN --mount=type=secret,id=cloudrepo,env=CLOUDREPO_TOKEN \
npm ci
COPY . .
CMD ["node", "index.js"]

/v2/_catalog is not implemented and answers 405 UNSUPPORTED. Use the admin portal to see what a repository holds.

docker login returns 401 Unauthorized

  • Use the repository token as the password, not your admin-portal password.
  • Check on the Repository Tokens page that the token has not been revoked or expired.

docker push returns 403 Forbidden after a successful login

  • The token cannot write to the repository. Pushing needs a Read + write token, and the user who created the token needs read and write access, not read only. Check the token’s scope on the Repository Tokens page.

docker push or docker pull cannot find the repository

  • Check the reference carries the literal repositories/ segment: <org>.mycloudrepo.io/repositories/<repo>/<image>:<tag>. Omitting it is the most common mistake, because docker login does not use it, so a successful login cannot warn you that the reference is malformed.

A group repository is a single address that resolves against several member repositories in a priority order you set. It gives a team one registry URL that serves images kept in more than one repository.

Create it in the admin portal with Docker as the format and Group as the kind, then add member repositories and order them. On a pull, CloudRepo tries each member in priority order and serves the first that has the image.

A group is addressed exactly like a local repository, with the same host and the same repositories/ segment:

Terminal
docker pull your-org.mycloudrepo.io/repositories/team-registry/my-app:1.0.0

A typical arrangement is one address for developers and two repositories behind it: app-images holding what your team builds, and vendor-images holding third-party images you have vetted and pushed yourself. Group team-registry lists them in that order, and developers configure one host and one repository name.

All Standard Settings apply.