You can use Depot Registry as your primary private container registry, including for images you build outside Depot. To move from GHCR, copy the images you still deploy, verify the copies, then update the jobs that push images and the services that pull them. You don't need to rebuild every release or move your source repositories to do it.
Let's walk through moving ghcr.io/acme/api:v1.2.3 to YOUR_ORG_ID.registry.depot.dev/services/api:v1.2.3. We'll copy the existing image, including its platforms, so the registry changes while the application stays the same.
Decide what to move
Start with one service and the releases it needs for deployment and rollback. Record each source tag, its digest, the destination repository, and the systems that pull it. Include production, staging, CI service containers, and local development scripts. An old release can still matter if it's your rollback target.
Depot gives each organization its own registry hostname. Repository names don't have to match GitHub repository names. In this example, the GitHub package acme/api becomes services/api in Depot.
| What changes | What to update |
|---|---|
| Registry hostname and repository | Image references in build jobs, deployment manifests, and scripts |
| Push credentials | A Depot registry access token scoped to the destination repository |
| Pull credentials | A separate pull-only token for each consuming environment |
| Access and cleanup rules | Configure them in Depot; copying image bytes doesn't copy GitHub package settings |
If you want to keep publishing to GHCR and accelerate pulls, use Depot Registry's pull-through cache. The steps below make Depot the primary registry for the service.
Authenticate to both registries
Install regctl for the copy and Docker for testing pulls. regctl can copy an image index and its platform manifests without loading just one platform into a local Docker image store.
For GHCR, use a GitHub personal access token (classic) with read:packages, from an account that can read the package. Authorize it for your organization's SSO if required. GitHub documents these requirements in its container registry guide.
In Depot's Registry settings, create a registry access token with Push permission and the exact repository scope services/api. Push permission includes reads, so this token can also verify the copy. Give it an expiration appropriate for the migration. See the registry quickstart for the settings.
Load the tokens into GHCR_READ_TOKEN and DEPOT_REGISTRY_PUSH_TOKEN through your secret manager. Run the following in Bash on a trusted machine. Replace the GitHub username and Depot organization ID. Keep shell tracing off so credentials aren't printed:
set -euo pipefail
set +x
GHCR_USER='YOUR_GITHUB_USERNAME'
DEPOT_REGISTRY='YOUR_ORG_ID.registry.depot.dev'
SOURCE='ghcr.io/acme/api'
DESTINATION="$DEPOT_REGISTRY/services/api"
TAG='v1.2.3'
printf '%s' "$GHCR_READ_TOKEN" |
regctl registry login ghcr.io --user "$GHCR_USER" --pass-stdin
printf '%s' "$DEPOT_REGISTRY_PUSH_TOKEN" |
regctl registry login "$DEPOT_REGISTRY" --user x-token --pass-stdinGitHub's GITHUB_TOKEN can read GHCR packages in workflows when the repository has access. It doesn't authenticate to Depot Registry. When you update a workflow, replace the registry login along with the image reference.
Copy the release by digest
Resolve the tag once, then copy that digest. If another job moves the tag during the migration, you'll still copy the release you selected.
SOURCE_DIGEST="$(regctl image digest "$SOURCE:$TAG")"
regctl image copy --referrers --digest-tags \
"$SOURCE@$SOURCE_DIGEST" "$DESTINATION:$TAG"
DESTINATION_DIGEST="$(regctl image digest "$DESTINATION:$TAG")"
if [ "$SOURCE_DIGEST" != "$DESTINATION_DIGEST" ]; then
printf 'Digest mismatch. Stop before switching consumers.\n' >&2
exit 1
fi
printf 'Verified image: %s@%s\n' "$DESTINATION" "$DESTINATION_DIGEST"The copy doesn't select a single platform. For a multi-platform image, it copies the index and the images it references. --referrers includes attached OCI artifacts, and --digest-tags includes artifacts stored under digest-based tags. See the regctl copy reference for these options.
Check the platforms your service actually runs. For an image published for both x86 and Arm:
for PLATFORM in linux/amd64 linux/arm64; do
SOURCE_PLATFORM="$(regctl image digest "$SOURCE@$SOURCE_DIGEST" --platform "$PLATFORM")"
DESTINATION_PLATFORM="$(regctl image digest "$DESTINATION@$DESTINATION_DIGEST" --platform "$PLATFORM")"
test "$SOURCE_PLATFORM" = "$DESTINATION_PLATFORM"
doneUse only the platforms present in your source image. Repeat the copy and checks for every release in your migration list. Copying one tag doesn't copy a repository's entire tag history.
If your deployment verifies signatures or attestations, run that same verification against the destination reference before cutover. Digest equality verifies the image manifest; it doesn't prove every attached artifact was copied or that a policy tied to a repository name will accept the new location. Artifacts stored in separate repositories need their own migration.
Switch consumers, then new builds
Create a separate Pull token scoped to services/api for the deployment environment. Store it in that environment's secret manager as DEPOT_REGISTRY_PULL_TOKEN and test a pull:
printf '%s' "$DEPOT_REGISTRY_PULL_TOKEN" |
docker login "$DEPOT_REGISTRY" --username x-token --password-stdin
docker pull "$DESTINATION@$DESTINATION_DIGEST"Change the image reference in a staging deployment to the verified Depot digest. Configure the runtime's registry credentials too, such as a Kubernetes image pull secret. Run your normal health checks and smoke tests before moving production. Keep the old GHCR reference and credentials available until you've tested rollback.
Next, change your existing build job's login and destination tag. A job using Docker can publish to the same custom repository:
printf '%s' "$DEPOT_REGISTRY_PUSH_TOKEN" |
docker login "$DEPOT_REGISTRY" --username x-token --password-stdin
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag "$DEPOT_REGISTRY/services/api:$RELEASE_TAG" \
--push .Set RELEASE_TAG to the release version or commit you already use. If the job uses Depot Container Builds, you can keep that build setup and change its output tag to the Depot registry reference. Registry migration doesn't require a CI migration.
Run the updated build job, verify the new release, and deploy it through the same staging checks. Once all consumers use Depot and your rollback releases are available there, retire the GHCR publishing job and revoke the temporary migration credentials. regctl registry logout ghcr.io and regctl registry logout "$DEPOT_REGISTRY" remove the copy tool's saved logins; clean up any temporary Docker logins too.
Set retention and check costs
Keep tagged production and rollback releases for as long as your deployment policy needs them. Review Depot's cleanup policies before enabling tagged-image deletion. A successful migration is no help if a later cleanup removes the release you need to roll back to.
Registry storage shares the plan's storage allowance with cache storage. The default Standard CDN mode has free ingress and egress; the optional Fast mode charges for outbound transfer and applies across the organization. Check Registry pricing and your usage using your stored image size and pull volume.
The final check is straightforward: a new release builds, pushes, pulls, and deploys from Depot, and a previous release can still be restored by digest. That's when the service is ready to stop depending on GHCR for its images.