How To Transfer GitLab Projects With Container Registry Images¶
The GitLab Project Transfer tool is used to move projects between different namespaces or groups (you can also move using the gitlab-project-factory). However, this tool will fail when a project has an active Container Registry.
This happens because registry data is stored separately from the repository code, and the transfer process cannot always reconcile existing tags or metadata in the target namespace.
To overcome this, we use a "Donor Repository" strategy. We first copy all images to a temporary "donor" repository to secure the data. This method is chosen over local storage for three reasons:
- It tests that the GitLab registry infrastructure can successfully accept the images before the move.
- It utilises the enterprise-grade reliability of the server.
- It eliminates the risk of hardware failure associated with storing large datasets locally.
- Storing critical data on someone's laptop is never a good idea!
Next, we delete the images from the original project's registry to bypass conflict errors, allowing the GitLab transfer to complete successfully. Once the move is finished, we copy the images back from the donor repo into the newly moved project.
Prerequisites¶
- Skopeo & jq: Ensure both are installed locally.
- Credentials: A GitLab Personal Access Token (PAT) with
read_registryandwrite_registryscopes. - Permissions: Owner/Maintainer access to both source and target.
brew install skopeo
brew install jq
Create Utility Scripts¶
Seed Registry¶
seed-registry.sh - This script will mirror all tags from the source to the target registry:
#!/bin/bash
# --- 1. Environment Validation ---
if [[ -z "$SOURCE" || -z "$DEST" || -z "$CREDS" ]]; then
echo "ā ERROR: Environment variables not set."
echo "Please run the following before executing:"
echo ' export SOURCE="registry.path/source"'
echo ' export DEST="registry.path/destination"'
echo ' export CREDS="crsid:token"'
exit 1
fi
echo "--- 2. Discovery Phase ---"
echo "Fetching tag list from: $SOURCE"
# Fetch tags and handle potential connection issues
TAG_LIST=$(skopeo inspect --creds "$CREDS" docker://"$SOURCE" | jq -r '.RepoTags[]' 2>/dev/null)
if [ -z "$TAG_LIST" ]; then
echo "ā ERROR: Could not retrieve tags. Check your path and credentials."
exit 1
fi
# Output the tags for confirmation
echo "The following tags will be copied:"
echo "------------------------------------------------------------"
echo "$TAG_LIST" | tr '\n' ' ' | sed 's/ $//' | sed 's/ / , /g'
echo -e "\n------------------------------------------------------------"
echo "Total tags found: $(echo "$TAG_LIST" | wc -l)"
# --- 3. Copy Phase ---
echo -e "\n--- 3. Starting Copy Phase ---"
for TAG in $TAG_LIST; do
echo "š Syncing tag: $TAG"
skopeo copy --multi-arch all \
--src-creds "$CREDS" \
--dest-creds "$CREDS" \
docker://"$SOURCE:$TAG" \
docker://"$DEST:$TAG"
done
echo -e "\nā
Seeding complete! Every tag has been mirrored to $DEST"
Verify Registry Integrity¶
verify-registry-integrity.sh - This script will verify that all tags in the target registry match
those in the source registry:
#!/bin/bash
# --- 1. Environment Check ---
if [[ -z "$SOURCE" || -z "$DEST" || -z "$CREDS" ]]; then
echo "ā ERROR: Environment variables (SOURCE, DEST, CREDS) are not set."
exit 1
fi
echo "--- Initializing Validation ---"
echo "Source: $SOURCE"
echo "Dest: $DEST"
# --- 2. Tag Discovery ---
# We fetch tags from the SOURCE to ensure the DEST has everything the SOURCE does.
TAG_LIST=$(skopeo inspect --creds "$CREDS" docker://"$SOURCE" | jq -r '.RepoTags[]')
echo "------------------------------------------------------------"
echo "Validating $(echo "$TAG_LIST" | wc -l) tags..."
echo "------------------------------------------------------------"
# --- 3. Integrity Check Loop ---
MATCH_COUNT=0
FAIL_COUNT=0
for TAG in $TAG_LIST; do
# Fetching only the Digest (SHA) for speed
SRC_SHA=$(skopeo inspect --creds "$CREDS" docker://"$SOURCE:$TAG" | jq -r '.Digest')
DEST_SHA=$(skopeo inspect --creds "$CREDS" docker://"$DEST:$TAG" | jq -r '.Digest' 2>/dev/null)
if [ "$SRC_SHA" == "$DEST_SHA" ]; then
echo "ā
[MATCH] $TAG"
((MATCH_COUNT++))
else
echo "ā [FAIL] $TAG"
echo " Source: $SRC_SHA"
echo " Dest: ${DEST_SHA:-NOT FOUND}"
((FAIL_COUNT++))
fi
done
# --- 4. Summary Report ---
echo "------------------------------------------------------------"
echo "Verification Summary:"
echo " Success: $MATCH_COUNT tags"
echo " Failed: $FAIL_COUNT tags"
echo "------------------------------------------------------------"
if [ $FAIL_COUNT -eq 0 ]; then
echo "ā
INTEGRITY VERIFIED: All images match perfectly."
else
echo "ā ļø WARNING: Integrity check failed. See details above."
exit 1
fi
chmod +x seed-registry.sh verify-registry-integrity.sh
Steps¶
For these steps, we're assuming the following, but obviously you should replace these with the actual paths and image names relevant to your project:
- Source project path: registry.gitlab.developers.cam.ac.uk/group_x/project_x
- Target project path: registry.gitlab.developers.cam.ac.uk/group_y/project_x
- Registry backup project path: registry.gitlab.developers.cam.ac.uk/group_y/registry-backup
- Image name: `my-image`
- Create a temporary project called
registry-backupin the target namespace. This will be used to hold the mirrored images during the transfer process.- we can use an existing donor project for this purpose if one is available, but it is recommended to create a new one to keep things tidy and avoid potential conflicts with existing images.
-
Set the required environment variables:
# The full path to the source registry export SOURCE="registry.gitlab.developers.cam.ac.uk/group_x/project_x/my-image" # The full path to the backup registry export DEST="registry.gitlab.developers.cam.ac.uk/group_y/registry-backup/my-image" # Your credentials in CRSid:PAT format export CREDS="<CRSid>:<Personal_Access_Token>" -
Do an initial test to check everything is setup OK by getting the list of tags on the image:
skopeo inspect --creds "$CREDS" docker://"$SOURCE" | jq -r '.RepoTags[]' -
Run the
seed-registry.shscript to mirror all tags from the source registry to the backup registry:./seed-registry.sh -
Run the
verify-registry-integrity.shscript to verify that all tags in the backup registry match those in the source registry:./verify-registry-integrity.sh -
If the integrity check fails, try running the
seed-registry.shscript again to see if it resolves the issue. - Delete the image(s) from the source registry to prevent conflicts during the transfer process.
- Transfer the project using the GitLab Project Transfer tool, moving it from
group_xtogroup_yor do this via gitlab-project-factory if you have it set up. -
After the transfer is complete, we need to modify the environment variables to point to the new registry location as the destination and the backup registry as the source:
# The full path to the backup registry export SOURCE="registry.gitlab.developers.cam.ac.uk/group_y/registry-backup/my-image" # The full path to the target registry export DEST="registry.gitlab.developers.cam.ac.uk/group_y/project_x" -
Run the
seed-registry.shscript to mirror all tags from the backup registry to the moved project registry:./seed-registry.sh -
Run the
verify-registry-integrity.shscript to verify that all tags in the moved project registry match those in the backup registry:./verify-registry-integrity.sh -
If a new backup project was created for this process, delete it to keep things tidy. If an existing donor project was used, you can choose to keep it as a backup in case you need to repeat the process in the future, or delete any images that were copied to it if you don't want to keep them around.