# Push Docker images to CloudRepo

> Log in to CloudRepo with a repository token, build and push an image to a Docker repository by its repositories path, push from CI, and fix a refused push.

Docker pushes to CloudRepo like it pushes to any registry: log in once to your organization’s host, then push an image whose name carries the repository. This page is for the person or pipeline that publishes images. To pull them, see [Pull Docker images](/docs/consume/docker.html).

## Before you start

- A Docker repository, as a local repository (not a proxy and not a group). If you have none, [create one](/docs/manage/repositories.html#creating-a-repository).
- A repository token that reaches it, with **Read + write**. A **Read only** token can pull but not push. See [Repository tokens](/docs/authenticate/repository-tokens.html) to create one. Use a token in the Generic format, which starts with `crp_v1_`: a token that `npm login` wrote (`crn_v1_`) does not authenticate at Docker.
- Your repository’s image name: `<organization>.mycloudrepo.io/repositories/<repository>/<image>:<tag>`. The organization’s name is the first part of your CloudRepo host, and the repository’s name is the one the admin portal shows. The repository’s [Connection Settings](/docs/manage/repositories.html#view-connection-settings) show the name with your values filled in.

## Push an image

**Docker**

**1. Log in to the host.** Pipe the token to `docker login` on standard input. CloudRepo does not check the username for Docker, but use your email address.

**Terminal**

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

Expected: `Login Succeeded`.

**2. Build and push.** The image name carries the repository: the host, the literal `repositories/` segment, your repository, then the image and its 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, give it that name first:

**Terminal**

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

**3. Check that it landed.** Ask the registry for the image you pushed:

**Terminal**

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

Expected: Docker finishes without an error and prints a `Digest:` line. The image is also listed in the repository in the [admin portal](https://admin.cloudrepo.io).

> **Log in to the host only:** `docker login` takes `your-org.mycloudrepo.io` and nothing more. The push and pull names add `/repositories/your-repo/`. The two look different on purpose.

## Push from CI

Keep the token in your CI’s secret store, expose it as the environment variable `CLOUDREPO_TOKEN`, and use the same login:

**Terminal**

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

`--password-stdin` is Docker’s option for reading the password from standard input instead of the command line. Never commit the token. The variable’s 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. The portal’s [Connection Settings](/docs/manage/repositories.html#view-connection-settings) show those snippets with your names filled in.

To install private Maven, Python or npm packages inside a `docker build`, hand the token to that build step as a build secret, never as a build argument. See [Docker repositories](/docs/formats/docker.html), “Install packages from CloudRepo in a Docker build”.

## Pushing a tag again

A Docker repository has no Overwrite Protection, and the card does not appear in its settings. Pushing a tag that already exists points that tag at the image you pushed, so give each release its own tag, and treat a tag like `latest` as one that moves.

## When a push is refused

Check these in order:

- **`docker login` answers 401 Unauthorized.** Use a repository token as the password, not the password you sign in to the admin portal with. Check on the **Repository Tokens** page that the token has not been revoked or expired. A token from `npm login` (`crn_v1_`) is refused here.
- **`docker push` answers 403 after a successful login.** The token cannot push to this repository. Pushing needs a **Read + write** token that reaches this repository, and the user who created the token must have read and write access, not read only. Check the token’s scope on the **Repository Tokens** page.
- **`docker push` is refused with 405.** The repository is a group or a proxy. CloudRepo answers a push to a group with `Writes are not permitted to group repositories`, and a push to a proxy repository with `Writes are not permitted to remote repositories`: here `remote` means a proxy. A group has no storage of its own, and a proxy repository takes no push either. Push to a local repository directly. A group that lists that repository as a member serves the image.
- **Docker cannot find the repository, or the registry refuses the name.** Check that the image name carries the literal `repositories/` segment, as in step 2: a name without it is refused as invalid (`NAME_INVALID`). A good login cannot tell you the name is wrong, because `docker login` names no repository.

More: [Repository tokens](/docs/authenticate/repository-tokens.html), for every client’s credential; [Pull Docker images](/docs/consume/docker.html), for the other direction; and [Docker repositories](/docs/formats/docker.html), for groups and the other settings.

---

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