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 versionconfirms it's available) - A GitHub personal access token (classic) with at least the
read:packagesscope 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 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.
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:
| 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
osandarchitecturein 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:
You should see:
Copy link to heading3. Authenticate to VCR
From your linked project directory, authenticate your container tool with VCR:
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:
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:
For single-platform images, a pull, tag, and push sequence works:
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:
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:
Then pull one image back and compare its digest to the GHCR original:
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:
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 atghcr.ioif you migrated those too. - External systems: Anything that pulled from GHCR with a
read:packagestoken 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:
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
latestfor 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.
Copy link to headingRelated resources and next steps
- Read the Vercel Container Registry documentation for the full push, pull, and repository management reference.
- Review Container Registry limits and pricing before migrating large repositories.
- Run your migrated images on Vercel Functions with container images or as custom Vercel Sandbox images.
- Manage repositories from the terminal with the vercel vcr CLI reference.