---
title: How to migrate from GHCR to Vercel Container Registry
description: Migrate container images from GitHub Container Registry (GHCR) to Vercel Container Registry (VCR), including authentication, multi-platform copies, and CI updates
url: /kb/guide/migrate-ghcr-to-vcr
canonical_url: "https://vercel.com/kb/guide/migrate-ghcr-to-vcr"
last_updated: 2026-07-29
authors: Ben Sabic
related:
  - /docs/container-registry
  - /docs/cli
  - /docs/container-registry/limits-and-pricing
  - /docs/functions/container-images
  - /docs/sandbox/concepts/images
  - /docs/container-registry/cli-reference
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Moving your images from [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) (GHCR) to [Vercel Container Registry](https://vercel.com/docs/container-registry) (VCR) puts them next to the products that run them. Vercel Functions and Vercel Sandbox pull directly from VCR, and every image you push is optimized in the background as a precompiled snapshot for Fluid compute. Because VCR implements the Docker Registry HTTP API v2 with the OCI format, the migration uses the `docker` commands you already run against `ghcr.io`.

This guide walks you through authenticating to both registries, copying your images, updating CI, and pointing your Vercel projects at the new references.

## Overview

In this guide, you'll learn how to:

- Map your GHCR image references to the VCR naming scheme
  
- Authenticate Docker to both GHCR and VCR
  
- Copy single-platform and multi-platform images between the registries
  
- Update a GitHub Actions workflow to push to VCR
  
- Verify the migrated images and update the services that consume them
  

## Prerequisites

Before you begin, make sure you have:

- A [Vercel account](https://vercel.com/signup) and the [Vercel CLI](https://vercel.com/docs/cli) installed (`npm i -g vercel`)
  

- Docker with Buildx installed locally (`docker buildx version` confirms it's available)
  
- A GitHub personal access token (classic) with at least the `read:packages` scope to pull private images from GHCR
  

## How it works

A VCR repository belongs to a Vercel project rather than to a user or organization. A full image reference includes the registry host, your team slug, your project slug, and the repository name with a tag or digest. This is the main structural difference from GHCR, where images are scoped to a personal account or organization:

|                     | GitHub Container Registry       | Vercel Container Registry                                                  |
| ------------------- | ------------------------------- | -------------------------------------------------------------------------- |
| Registry host       | `ghcr.io`                       | `vcr.vercel.com`                                                           |
| Namespace           | User or organization            | Team slug + project slug                                                   |
| Example reference   | `ghcr.io/acme/api-server:1.4.0` | `vcr.vercel.com/acme/checkout/api-server:1.4.0`                            |
| Repository creation | On first push                   | On first push, or from the **Images** tab in the project dashboard         |
| Authentication      | Personal access token (classic) | OIDC via `vercel vcr login` (12-hour credentials) or a Vercel access token |
| API                 | Docker Registry HTTP API v2     | Docker Registry HTTP API v2 (OCI format)                                   |

Repository names in VCR can include lowercase letters, numbers, periods, underscores, and dashes, and can't start or end with a period, underscore, or dash. Most GHCR repository names carry over unchanged.

## Steps

### 1\. Check your images against VCR limits

Before copying anything, confirm your images fit within VCR's size and plan limits:

| Resource               | Limit  |
| ---------------------- | ------ |
| Compressed image layer | 500 MB |
| Total image size       | 15 GB  |
| Manifest body          | 4 MB   |
| Image config blob      | 1 MB   |

Plan limits also apply. On the Pro plan, each project supports up to 1,000 repositories with 10,000 images and 10,000 tags per repository. Hobby projects support 10 repositories with 50 images and 1,000 tags each.

Two compatibility rules matter for migrated images:

- Layers must use gzip or zstd compression. GHCR images built with standard Docker tooling use gzip, which VCR accepts, so existing images copy over without a rebuild.
  
- Single-platform manifests must declare `os` and `architecture` in the image config. Images built with Docker or Buildx include these by default.
  

VCR image storage costs $0.10 per GB. You can decide per repository whether to migrate full tag history or only the tags still in use.

### 2\. Authenticate to GHCR

Sign in to GHCR with your personal access token so Docker can pull your existing images:

`echo $CR_PAT | docker login ghcr.io -u your_github_username --password-stdin`

You should see:

`Login Succeeded`

### 3\. Authenticate to VCR

From your linked project directory, authenticate your container tool with VCR:

`vercel vcr login docker`

Vercel mints a short-lived, project-scoped OIDC token and passes it to Docker with the username `oidc`. The credentials are valid for 12 hours, so re-run the command if a later push fails with an authentication error. The command also supports Podman and Buildah, and you can pass `--project` to authenticate against a different project.

For non-interactive environments, use a Vercel access token from the [Account Tokens page](https://vercel.com/account/tokens) instead:

`printf '%s' "$VERCEL_TOKEN" | docker login vcr.vercel.com \ --username "$VERCEL_TEAM_ID" \ --password-stdin`

With token authentication, the Docker username is the team ID that owns the project.

### 4\. Copy your images

You don't need to create repositories ahead of time. VCR creates the repository automatically on push when your authenticated account has access to the project.

**For multi-platform images (recommended)**, copy the manifest list directly between registries with Buildx. This preserves every platform variant without pulling layers to your machine:

`docker buildx imagetools create \ --tag vcr.vercel.com/your_team_slug/your_project_slug/api-server:1.4.0 \ ghcr.io/your_github_org/api-server:1.4.0`

**For single-platform images**, a pull, tag, and push sequence works:

`docker pull ghcr.io/your_github_org/api-server:1.4.0 docker tag ghcr.io/your_github_org/api-server:1.4.0 \ vcr.vercel.com/your_team_slug/your_project_slug/api-server:1.4.0 docker push vcr.vercel.com/your_team_slug/your_project_slug/api-server:1.4.0`

Note that `docker pull` retrieves only your local platform's variant of a multi-platform image, so a subsequent push flattens the image to that single platform. Use the `imagetools` approach whenever a tag serves more than one architecture.

Repeat for each tag you want to keep. To migrate several tags of the same repository, loop over them:

`for tag in 1.2.0 1.3.0 1.4.0; do docker buildx imagetools create \ --tag "vcr.vercel.com/your_team_slug/your_project_slug/api-server:$tag" \ "ghcr.io/your_github_org/api-server:$tag" done`

If you plan to boot the image in Vercel Sandbox, it must include a `linux/amd64` manifest. VCR prepares an optimized `linux/amd64` build after the push and reports a readiness state on the repository details page, so wait for preparation to finish before creating sandboxes from the image.

### 5\. Verify the migrated images

List the repositories in your project to confirm the push landed:

`vercel vcr ls`

Then pull one image back and compare its digest to the GHCR original:

`docker buildx imagetools inspect \ vcr.vercel.com/your_team_slug/your_project_slug/api-server:1.4.0`

The manifest digest should match the digest reported by the same command against the `ghcr.io` reference. Matching digests confirm the copy is byte-identical.

### 6\. Update your GitHub Actions workflow

If a workflow currently builds and pushes to GHCR, switch the login step and image name to VCR. Store your Vercel access token as a repository secret (for example, `VERCEL_TOKEN`) and your team ID as a variable:

`env: REGISTRY: vcr.vercel.com IMAGE_NAME: your_team_slug/your_project_slug/api-server jobs: build-and-push: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v6 - name: Log in to Vercel Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ vars.VERCEL_TEAM_ID }} password: ${{ secrets.VERCEL_TOKEN }} - name: Build and push image uses: docker/build-push-action@v6 with: push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}`

Compared to the GHCR version of this workflow, the `GITHUB_TOKEN` and `packages: write` permission are no longer needed for the registry step. If you build inside the Vercel build environment instead, no token setup is required because the environment is already authenticated for the current project.

### 7\. Update the services that pull your images

Point each consumer at the new reference:

- **Vercel Sandbox**: Update the image passed to `Sandbox.create()` to the VCR repository reference. Sandbox resolves the reference against VCR for the authenticated project.
  
- **Vercel Functions**: If you deploy a container image with a `Dockerfile.vercel`, Vercel builds and pushes to VCR during the deployment, so no reference change is needed. Update any base images in the Dockerfile that pointed at `ghcr.io` if you migrated those too.
  
- **External systems**: Anything that pulled from GHCR with a `read:packages` token now needs a Vercel access token with access to the project scope.
  

Keep the GHCR repositories in place until every consumer pulls successfully from VCR, then archive or delete them on the GitHub side.

## Troubleshooting

### Push fails with an authentication error

OIDC credentials from `vercel vcr login` expire after 12 hours. Re-run the command to refresh them. For token-based logins, confirm the token has access to the project scope and that the Docker username is the team ID, not your personal username.

### Push is rejected because of layer compression

VCR rejects uncompressed OCI layers. This can happen with images produced by tooling that skips compression. Rebuild the image with zstd compression, which Vercel recommends for VCR:

`docker buildx build \ --platform linux/amd64 \ --output "type=image,name=vcr.vercel.com/your_team_slug/your_project_slug/api-server:1.4.0,push=true,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true" \`

### Sandbox returns image\_not\_ready

VCR serves an image to Sandbox only after it has prepared an optimized `linux/amd64` build. Check the readiness state on the repository details page in your dashboard, and retry `Sandbox.create()` after preparation finishes.

### Repository name is rejected

VCR repository names accept lowercase letters, numbers, periods, underscores, and dashes only, and can't start or end with a period, underscore, or dash. Rename repositories with uppercase characters or leading separators during the tag step.

## Best practices

- **Use immutable tags for production images**: Tag production images with a commit SHA and reserve `latest` for local iteration, so a migrated reference always resolves to the same content.
  
- **Migrate in order of risk**: Copy images consumed by preview environments first, verify pulls, then cut over production consumers.
  
- **Prune before you copy**: Storage costs $0.10 per GB, so skip tags that no environment references rather than mirroring full history.
  
- **Push from the Vercel build environment when possible**: It's pre-authenticated for the current project, which removes token management from CI entirely.
  

## Related resources and next steps

- Read the [Vercel Container Registry documentation](https://vercel.com/docs/container-registry) for the full push, pull, and repository management reference.
  
- Review [Container Registry limits and pricing](https://vercel.com/docs/container-registry/limits-and-pricing) before migrating large repositories.
  
- Run your migrated images on [Vercel Functions with container images](https://vercel.com/docs/functions/container-images) or as [custom Vercel Sandbox images](https://vercel.com/docs/sandbox/concepts/images).
  
- Manage repositories from the terminal with the vercel vcr [CLI reference](https://vercel.com/docs/container-registry/cli-reference).