Skip to content

Instantly share code, notes, and snippets.

@Sieboldianus
Last active August 8, 2025 06:13
Show Gist options
  • Select an option

  • Save Sieboldianus/be5dbb57cdada3ea8af4b1512a6e8e41 to your computer and use it in GitHub Desktop.

Select an option

Save Sieboldianus/be5dbb57cdada3ea8af4b1512a6e8e41 to your computer and use it in GitHub Desktop.
Migrate all Gitlab Registry Container images from one Gitlab to another (Gitlab, quai.io etc.)

I had to migrate all container images stored in one Gitlab registry () to another ().

Here is the script. How to use it:

  1. Login to the registries ahead of time in bash, e.g.
docker login gcr.hrz.tu-chemnitz.de
# also create a read-token for the source repo and export in your current shell
export SOURCE_TOKEN=...
QUAY_USERNAME="ioer-fdz+registry_migration"
QUAY_PASSWORD="..."
echo "$QUAY_PASSWORD" | docker login -u "$QUAY_USERNAME" --password-stdin quay.io

Make sure you use your username and an Access Token, created ahead of time, as the password.

Then run the script on any capable VM:

chmod +x migrate_registry.sh
./migrate_registry.sh

The script will sequentially check if images have been pulled already, and if so, pull, retag and push images. After each loop, any local images that are no longer needed will be deleted to reduce the total amount of local storage used.

#!/bin/bash
# --- Configuration ---
# Stop the script if any command fails
set -e
# 1. Source Registry Configuration
SOURCE_REGISTRY="gcr.hrz.tu-chemnitz.de"
SOURCE_IMAGE_PATH="ioer/fdz/carto-lab-docker"
SOURCE_USER="[YOUR-GL-USERNAME]"
# 2. Destination Registry Configuration
DEST_REGISTRY="quay.io"
DEST_IMAGE_PATH="ioer-fdz/carto-lab-docker"
# 3. Filtering
# Use a grep extended regular expression to EXCLUDE tags you don't want.
# This example excludes the 'latest' tag. Use '|' to separate multiple patterns.
# For example: EXCLUDE_FILTER="latest|dev|test"
EXCLUDE_FILTER="latest"
# --- Script Logic ---
# This script assumes you have ALREADY LOGGED IN to the DESTINATION registry.
# > docker login gcr.hrz.tu-chemnitz.de
echo "--- Authenticating with Source Registry to get tag list... ---"
# Step 1: Get the authentication realm and service from the source registry.
# The registry will respond with a 401 and a Www-Authenticate header telling us where to get a token.
AUTH_HEADER=$(curl -s -I "https://_:$SOURCE_TOKEN@$SOURCE_REGISTRY/v2/$SOURCE_IMAGE_PATH/tags/list" | grep -i Www-Authenticate)
REALM=$(echo "$AUTH_HEADER" | grep -o 'realm="[^"]*"' | sed 's/realm="\([^"]*\)"/\1/')
SERVICE=$(echo "$AUTH_HEADER" | grep -o 'service="[^"]*"' | sed 's/service="\([^"]*\)"/\1/')
# Step 2: Request an authentication token from the auth service.
# We use the username and token here to get a temporary bearer token.
TOKEN=$(curl -s -u "$SOURCE_USER:$SOURCE_TOKEN" "$REALM?service=$SERVICE&scope=repository:$SOURCE_IMAGE_PATH:pull" | jq -r '.token')
# Step 3: Use the bearer token to get the list of all tags.
# The `jq -r '.tags[]'` command extracts each tag and prints it on a new line.
ALL_TAGS=$(curl -s -H "Authorization: Bearer $TOKEN" "https://$SOURCE_REGISTRY/v2/$SOURCE_IMAGE_PATH/tags/list" | jq -r '.tags[]')
# Step 4: Filter out the unwanted tags and load them into our array.
TAGS_TO_MIGRATE=($(echo "$ALL_TAGS" | grep -E -v "^($EXCLUDE_FILTER)$"))
echo "Found ${#TAGS_TO_MIGRATE[@]} tags to migrate after filtering."
echo ""
# The rest of the script is the sequential migration logic from before.
SOURCE_IMAGE_BASE="$SOURCE_REGISTRY/$SOURCE_IMAGE_PATH"
DEST_IMAGE_BASE="$DEST_REGISTRY/$DEST_IMAGE_PATH"
# Function to check if a tag exists on Quay.io
tag_exists_in_destination() {
local tag=$1
curl -s -f -L "https://quay.io/v2/${DEST_IMAGE_PATH}/manifests/${tag}" > /dev/null
}
for TAG in "${TAGS_TO_MIGRATE[@]}"; do
SOURCE_IMAGE_FULL="$SOURCE_IMAGE_BASE:$TAG"
DEST_IMAGE_FULL="$DEST_IMAGE_BASE:$TAG"
if tag_exists_in_destination "$TAG"; then
echo "✔ Tag '$TAG' already exists in destination — skipping."
echo ""
continue
fi
echo "--- Processing Tag: $TAG ---"
# Step 1: Pull a single image from the source
echo "Pulling $SOURCE_IMAGE_FULL..."
docker pull "$SOURCE_IMAGE_FULL"
# Step 2: Re-tag the image for the new destination
echo "Re-tagging for $DEST_IMAGE_FULL..."
docker tag "$SOURCE_IMAGE_FULL" "$DEST_IMAGE_FULL"
# Step 3: Push the single image to the destination
echo "Pushing $DEST_IMAGE_FULL..."
docker push "$DEST_IMAGE_FULL"
# Step 4: Remove the local images to free up disk space immediately
echo "Cleaning up local images..."
docker rmi "$SOURCE_IMAGE_FULL" || true
docker rmi "$DEST_IMAGE_FULL" || true
echo "Successfully migrated tag: $TAG"
echo ""
done
echo "--- All specified images have been migrated successfully! ---"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment