# Docker Repositories

> Set up a private Docker registry with CloudRepo. docker login, tag, push and pull, CI/CD token patterns, and group repositories.

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`.

## Before you start

- A Docker repository. If you have none, see [Creating a Repository](/docs/manage/repositories.html#creating-a-repository).
- A repository token that reaches it, with **Read + write** if you will push. See [Repository Tokens: Create One and Authenticate](/docs/authenticate/repository-tokens.html). 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](/docs/manage/repositories.html#view-connection-settings) of the repository show its image reference and ready-made CI snippets with your names filled in.

> **Note:** Use a repository token as the password at `docker login`, not the password you sign in to the admin portal with.

## Connect Docker

**Docker**

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

**Terminal**

```bash
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**

```bash
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**

```bash
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**

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

> **Log in to the host only:** `docker login` takes `your-org.mycloudrepo.io` and nothing more. The pull and push references add `/repositories/your-repo/`. The two commands deliberately look different from each other.

## Use Docker in CI

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**

```bash
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

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](https://docs.docker.com/build/building/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**

```bash
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](/docs/authenticate/repository-tokens.html) shows both files).

`settings.xml (next to the Dockerfile)`

```xml
<settings>
  <servers>
    <server>
      <id>cloudrepo</id>
      <username>${env.CLOUDREPO_USERNAME}</username>
      <password>${env.CLOUDREPO_TOKEN}</password>
    </server>
  </servers>
</settings>
```

**Dockerfile - Maven**

```dockerfile
# 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**

```dockerfile
# 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`

```ini
registry=https://your-org.mycloudrepo.io/repositories/your-repo/
//your-org.mycloudrepo.io/repositories/your-repo/:_authToken=${CLOUDREPO_TOKEN}
```

**Dockerfile - npm**

```dockerfile
# 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"]
```

## What the registry does not serve

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

## Troubleshooting

**`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.

## Group Docker repositories

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**

```bash
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.

> **Groups are read-only:** `docker push` to a group is rejected: a group has no storage of its own. Push to the member repository directly, and the image is visible through the group.

## Docker repository settings

### Other settings

All [Standard Settings](/docs/manage/repositories.html#repository-settings) apply.

---

The page: https://www.cloudrepo.io/docs/formats/docker.html
