Skip to content
Docs

How to migrate from GHCR to Vercel Container Registry

Migrate container images from GitHub Container Registry (GHCR) to Vercel Container Registry (VCR), including authentication, multi-platform copies, and CI updates

Ben SabicContent Engineer

Moving your images from GitHub Container Registry (GHCR) to Vercel 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.

Copy link to headingOverview

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

Copy link to headingPrerequisites

Before you begin, make sure you have:

  • A Vercel account and the Vercel 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

Copy link to headingHow 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 RegistryVercel Container Registry
Registry hostghcr.iovcr.vercel.com
NamespaceUser or organizationTeam slug + project slug
Example referenceghcr.io/acme/api-server:1.4.0vcr.vercel.com/acme/checkout/api-server:1.4.0
Repository creationOn first pushOn first push, or from the Images tab in the project dashboard
AuthenticationPersonal access token (classic)OIDC via vercel vcr login (12-hour credentials) or a Vercel access token
APIDocker Registry HTTP API v2Docker 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.

Copy link to headingSteps

Copy link to heading1. Check your images against VCR limits

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

ResourceLimit
Compressed image layer500 MB
Total image size15 GB
Manifest body4 MB
Image config blob1 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.

Copy link to heading2. 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

Copy link to heading3. 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 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.

Copy link to heading4. 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.

Copy link to heading5. 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.

Copy link to heading6. 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:

.github/actions/docker.yml
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.

Copy link to heading7. 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.

Copy link to headingTroubleshooting

Copy link to headingPush 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.

Copy link to headingPush 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" \

Copy link to headingSandbox 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.

Copy link to headingRepository 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.

Copy link to headingBest 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.

More Vercel Container Registry guides