Registry v2: Migrating from the Legacy ACP Registry
Migration from the legacy ACP Registry is an ACP-specific topic. It is not part of the OpenShift Registry chapter structure, but it is required when an existing ACP environment moves image workloads from the legacy Registry to Registry v2 in image-registry-system. Registry v2 means the Registry deployed and managed by cluster-image-registry-operator.
The migration workflow is based on the validated ACP ac runbook. No dedicated Registry migration subcommand is required. Use the following ac commands:
ac get images to export the legacy image list.
ac registry login to write OCI registry credentials.
ac image mirror to copy image blobs when the source and target registries do not share storage.
ac image info to read image digests and media types from the registry selected for metadata inspection.
ac create -f to create ImageStreamMapping resources and backfill Registry v2 Image and ImageStream metadata.
During migration, use a maintenance window when possible. At minimum, pause writes to the legacy Registry. Reads can remain available. If new images are pushed during migration, repeat image copy and metadata backfill before the final cutover.
Migration Scenarios
Prepare the following inputs before running the migration:
Create a migration workspace:
export MIGRATION_DIR=/tmp/registry-migration
mkdir -p "$MIGRATION_DIR"
export MIGRATION_KUBECONFIG="$MIGRATION_DIR/kubeconfig"
export IMAGE_LIST_FILE="$MIGRATION_DIR/image-list.txt"
export IMAGE_DIGEST_FILE="$MIGRATION_DIR/image-digests.txt"
export MAPPING_FILE="$MIGRATION_DIR/mirror-map.txt"
export METADATA_DIR="$MIGRATION_DIR/metadata"
export REGISTRY_AUTH_DIR="$MIGRATION_DIR/registry-auth"
export REGISTRY_AUTH_FILE="$REGISTRY_AUTH_DIR/config.json"
export REGISTRY_INSECURE_FLAG="${REGISTRY_INSECURE_FLAG:---insecure}"
mkdir -p "$REGISTRY_AUTH_DIR" "$METADATA_DIR"
If the legacy Registry uses HTTP, derive LEGACY_REGISTRY_URL from LEGACY_REGISTRY_HOST. If it uses HTTPS, set the full URL explicitly:
export LEGACY_REGISTRY_URL="${LEGACY_REGISTRY_URL:-http://$LEGACY_REGISTRY_HOST}"
REGISTRY_AUTH_DIR stores an OCI registry auth file. Setting this variable does not start a local container runtime.
If both source and target registries use trusted HTTPS certificates, disable the insecure flag:
export REGISTRY_INSECURE_FLAG=""
Log In and Check Registry Mode
Log in to ACP and create a migration-only kubeconfig:
LOGIN_ARGS=(
ac login "$ACP_PLATFORM_URL"
--name "$ACP_SESSION_NAME"
--username "$ACP_USERNAME"
--password "$ACP_PASSWORD"
--kubeconfig "$MIGRATION_KUBECONFIG"
)
[ -z "${ACP_IDP:-}" ] || LOGIN_ARGS+=(--idp "$ACP_IDP")
[ -z "${ACP_AUTH_TYPE:-}" ] || LOGIN_ARGS+=(--auth-type "$ACP_AUTH_TYPE")
"${LOGIN_ARGS[@]}"
export KUBECONFIG="$MIGRATION_KUBECONFIG"
[ -z "${ACP_CLUSTER:-}" ] || ac config use-cluster "$ACP_CLUSTER"
If the cluster has both legacy and Registry v2 backends, auto mode can require an explicit selection. Start migration in legacy mode and confirm that the legacy Registry can be discovered:
ac config set-registry-mode legacy
ac config get-registry-mode
ac registry info
ac registry info --internal
ac registry info shows the Registry discovered for the current mode. It might return an in-cluster Service such as image-registry.cpaas-system, which is not directly reachable from an administrator workstation. Use LEGACY_REGISTRY_URL for image discovery and use LEGACY_REGISTRY_HOST and TARGET_REGISTRY_HOST for image copy.
Prepare the Image List
Export the legacy image list visible to the current ACP user:
ac config set-registry-mode legacy
ac get images \
--registry-url "$LEGACY_REGISTRY_URL" \
> "$MIGRATION_DIR/legacy-images.txt"
Generate the image list and the optional digest list from the ac get images table output:
awk 'NR > 1 && NF >= 2 { print $1 ":" $2 }' \
"$MIGRATION_DIR/legacy-images.txt" \
| sort -u > "$IMAGE_LIST_FILE"
awk 'NR > 1 && NF >= 4 && $4 != "unknown" { print $1 ":" $2 " " $4 }' \
"$MIGRATION_DIR/legacy-images.txt" \
| sort -u > "$IMAGE_DIGEST_FILE"
You can also write the list manually when only selected images should be migrated:
cat >> "$IMAGE_LIST_FILE" <<EOF
your-namespace/your-image:your-tag
EOF
cat >> "$IMAGE_DIGEST_FILE" <<EOF
your-namespace/your-image:your-tag sha256:<digest>
EOF
Review the generated list before continuing:
sed -n '1,20p' "$IMAGE_LIST_FILE"
sed -n '1,20p' "$IMAGE_DIGEST_FILE"
wc -l "$IMAGE_LIST_FILE"
Remove temporary test images, retired namespaces, and incorrect tags from the list.
Write Registry Credentials
Write credentials for the legacy and target registries. ac image mirror and ac image info read this file:
export DOCKER_CONFIG="$REGISTRY_AUTH_DIR"
ac registry login \
--registry "$LEGACY_REGISTRY_HOST" \
--skip-check \
--to "$REGISTRY_AUTH_FILE" \
$REGISTRY_INSECURE_FLAG
ac registry login \
--registry "$TARGET_REGISTRY_HOST" \
--skip-check \
--to "$REGISTRY_AUTH_FILE" \
$REGISTRY_INSECURE_FLAG
If the source and target registries belong to different ACP environments, write both credentials into the same auth file with the corresponding kubeconfig:
KUBECONFIG="$SOURCE_KUBECONFIG" \
ac registry login --registry "$LEGACY_REGISTRY_HOST" --skip-check --to "$REGISTRY_AUTH_FILE" $REGISTRY_INSECURE_FLAG
KUBECONFIG="$TARGET_KUBECONFIG" \
ac registry login --registry "$TARGET_REGISTRY_HOST" --skip-check --to "$REGISTRY_AUTH_FILE" $REGISTRY_INSECURE_FLAG
Registry v2 needs Image API metadata in addition to image blobs. Use ImageStreamMapping to backfill Image, ImageStream, and tag metadata.
Define the metadata backfill functions in the migration shell session:
ensure_imagestream() {
namespace="$1"
stream="$2"
stream_file="$METADATA_DIR/${namespace}-${stream}.imagestream.yaml"
stream_output_file="$METADATA_DIR/${namespace}-${stream}.imagestream.apply.txt"
if ac get imagestreams.image.alauda.io "$stream" -n "$namespace" >/dev/null 2>&1; then
return 0
fi
cat > "$stream_file" <<EOF
apiVersion: image.alauda.io/v1
kind: ImageStream
metadata:
name: $stream
namespace: $namespace
spec:
lookupPolicy:
local: false
EOF
if ac create -f "$stream_file" > "$stream_output_file" 2>&1; then
return 0
fi
echo "[metadata] failed to create ImageStream $namespace/$stream; output=$stream_output_file" >&2
sed -n '1,20p' "$stream_output_file" >&2
return 1
}
write_imagestream_mapping() {
image="$1"
digest="${2:-}"
namespace="${image%%/*}"
rest="${image#*/}"
stream="${rest%%:*}"
tag="${rest##*:}"
inspect_registry_host="${METADATA_INSPECT_REGISTRY_HOST:-$TARGET_REGISTRY_HOST}"
source_ref="$inspect_registry_host/$image"
repo_ref="$TARGET_REGISTRY_HOST/$namespace/$stream"
inspect_ref="$source_ref"
name_prefix="${namespace}-${stream}-${tag}"
info_file="$METADATA_DIR/${name_prefix}.info.txt"
mapping_file="$METADATA_DIR/${name_prefix}.mapping.yaml"
apply_output_file="$METADATA_DIR/${name_prefix}.apply.txt"
BACKFILL_LAST_ACTION=""
if [ -n "$digest" ]; then
inspect_ref="$inspect_registry_host/$namespace/$stream@$digest"
fi
ac image info "$inspect_ref" $REGISTRY_INSECURE_FLAG > "$info_file"
[ -n "$digest" ] || digest="$(awk '/^Digest:/ { sub(/^Digest:[[:space:]]*/, ""); print; exit }' "$info_file")"
media_type="$(awk '/^Media Type:/ { sub(/^Media Type:[[:space:]]*/, ""); print; exit }' "$info_file")"
[ -n "$media_type" ] || media_type="application/vnd.docker.distribution.manifest.v2+json"
if [ -z "$digest" ]; then
echo "failed to read digest from $source_ref" >&2
return 1
fi
ensure_imagestream "$namespace" "$stream" || return 1
current_line="$(
ac get imagestreamtags "$stream:$tag" -n "$namespace" 2>/dev/null \
| awk 'NR > 1 && $3 == "current" { print $4 " " $6; exit }'
)"
current_digest="${current_line%% *}"
current_reference="${current_line#* }"
expected_reference="$repo_ref@$digest"
if [ "$current_digest" = "$digest" ] && [ "$current_reference" = "$expected_reference" ]; then
BACKFILL_LAST_ACTION="skipped"
return 0
fi
cat > "$mapping_file" <<EOF
apiVersion: image.alauda.io/v1
kind: ImageStreamMapping
metadata:
name: $stream
namespace: $namespace
image:
metadata:
name: "$digest"
dockerImageReference: "$repo_ref@$digest"
dockerImageManifestMediaType: "$media_type"
tag: "$tag"
EOF
if ac create -f "$mapping_file" > "$apply_output_file" 2>&1; then
BACKFILL_LAST_ACTION="applied"
return 0
fi
BACKFILL_LAST_ACTION="failed"
echo "[metadata] failed $image; output=$apply_output_file" >&2
sed -n '1,20p' "$apply_output_file" >&2
return 1
}
backfill_metadata() {
total=0
success=0
applied=0
skipped=0
failed=0
progress_interval="${METADATA_PROGRESS_INTERVAL:-100}"
verbose="${METADATA_VERBOSE:-false}"
if [ -n "${IMAGE_DIGEST_FILE:-}" ] && [ -s "$IMAGE_DIGEST_FILE" ]; then
echo "[metadata] start backfill source=$IMAGE_DIGEST_FILE output_dir=$METADATA_DIR progress_interval=$progress_interval"
while read -r image digest; do
[ -n "$image" ] || continue
total=$((total + 1))
if write_imagestream_mapping "$image" "$digest"; then
success=$((success + 1))
[ "$BACKFILL_LAST_ACTION" = "applied" ] && applied=$((applied + 1))
[ "$BACKFILL_LAST_ACTION" = "skipped" ] && skipped=$((skipped + 1))
else
failed=$((failed + 1))
fi
if [ "$verbose" = "true" ]; then
echo "[metadata] [$total] $image action=${BACKFILL_LAST_ACTION:-unknown}"
elif [ "$progress_interval" -gt 0 ] && [ $((total % progress_interval)) -eq 0 ]; then
echo "[metadata] progress: total=$total success=$success applied=$applied skipped=$skipped failed=$failed"
fi
done < "$IMAGE_DIGEST_FILE"
elif [ -n "${IMAGE_LIST_FILE:-}" ] && [ -s "$IMAGE_LIST_FILE" ]; then
echo "[metadata] start backfill source=$IMAGE_LIST_FILE output_dir=$METADATA_DIR progress_interval=$progress_interval"
while read -r image; do
[ -n "$image" ] || continue
total=$((total + 1))
if write_imagestream_mapping "$image"; then
success=$((success + 1))
[ "$BACKFILL_LAST_ACTION" = "applied" ] && applied=$((applied + 1))
[ "$BACKFILL_LAST_ACTION" = "skipped" ] && skipped=$((skipped + 1))
else
failed=$((failed + 1))
fi
if [ "$verbose" = "true" ]; then
echo "[metadata] [$total] $image action=${BACKFILL_LAST_ACTION:-unknown}"
elif [ "$progress_interval" -gt 0 ] && [ $((total % progress_interval)) -eq 0 ]; then
echo "[metadata] progress: total=$total success=$success applied=$applied skipped=$skipped failed=$failed"
fi
done < "$IMAGE_LIST_FILE"
else
echo "[metadata] no image list found; check IMAGE_DIGEST_FILE or IMAGE_LIST_FILE" >&2
return 1
fi
echo "[metadata] summary: total=$total success=$success applied=$applied skipped=$skipped failed=$failed output_dir=$METADATA_DIR"
[ "$failed" -eq 0 ]
}
Confirm that the function exists in the current shell:
if type backfill_metadata >/dev/null 2>&1; then
echo "backfill_metadata loaded"
fi
For large migrations, the function prints progress every 100 images by default. Adjust the interval if needed:
export METADATA_PROGRESS_INTERVAL=500
Set METADATA_VERBOSE=true only when troubleshooting a small set of images.
Scenario A: Reuse Legacy Storage
Use this path when the legacy Registry and Registry v2 run in the same ACP environment and business cluster, and Registry v2 can be configured to access the same blob storage that the legacy Registry uses.
This path does not copy image blobs. However, do not assume that Registry v2 can read old blobs by tag before metadata exists. The correct sequence is:
- Pause writes to the legacy Registry.
- Attach Registry v2 to the legacy blob storage.
- Read digest and media type from the legacy Registry.
- Create
ImageStream and ImageStreamMapping objects in Registry v2.
- Verify reads through Registry v2.
Do not run prune or Registry GC against either Registry until migration has been accepted.
Set the Registry namespaces:
export LEGACY_REGISTRY_NAMESPACE="${LEGACY_REGISTRY_NAMESPACE:-cpaas-system}"
export MODERN_REGISTRY_NAMESPACE="${MODERN_REGISTRY_NAMESPACE:-image-registry-system}"
Filesystem or PVC Storage Reuse
For filesystem storage such as NFS or CephFS, make the Registry v2 PVC point to the same legacy Registry data. The exact PV/PVC manifest depends on the storage backend. The example below applies only when the legacy Registry uses an NFS CSI PV.
Locate the legacy Registry PVC and PV:
export LEGACY_REGISTRY_PVC="${LEGACY_REGISTRY_PVC:-image-registry}"
export MODERN_REGISTRY_PVC="${MODERN_REGISTRY_PVC:-image-registry-storage}"
export REUSE_PV_NAME="${REUSE_PV_NAME:-image-registry-legacy-reuse}"
LEGACY_PV_NAME="$(
ac get pvc "$LEGACY_REGISTRY_PVC" -n "$LEGACY_REGISTRY_NAMESPACE" \
-o jsonpath='{.spec.volumeName}'
)"
test -n "$LEGACY_PV_NAME"
ac get pvc "$LEGACY_REGISTRY_PVC" -n "$LEGACY_REGISTRY_NAMESPACE"
ac get pv "$LEGACY_PV_NAME"
Confirm that the legacy PV uses the NFS CSI driver and collect the fields needed to reuse it:
LEGACY_STORAGE_DRIVER="$(
ac get pv "$LEGACY_PV_NAME" -o jsonpath='{.spec.csi.driver}'
)"
test "$LEGACY_STORAGE_DRIVER" = "nfs.csi.k8s.io"
LEGACY_NFS_SERVER="$(
ac get pv "$LEGACY_PV_NAME" -o jsonpath='{.spec.csi.volumeAttributes.server}'
)"
LEGACY_NFS_SHARE="$(
ac get pv "$LEGACY_PV_NAME" -o jsonpath='{.spec.csi.volumeAttributes.share}'
)"
LEGACY_NFS_SUBDIR="$(
ac get pv "$LEGACY_PV_NAME" -o jsonpath='{.spec.csi.volumeAttributes.subdir}'
)"
LEGACY_NFS_VOLUME_HANDLE="$(
ac get pv "$LEGACY_PV_NAME" -o jsonpath='{.spec.csi.volumeHandle}'
)"
LEGACY_STORAGE_SIZE="$(
ac get pv "$LEGACY_PV_NAME" -o jsonpath='{.spec.capacity.storage}'
)"
test -n "$LEGACY_NFS_SERVER"
test -n "$LEGACY_NFS_SHARE"
test -n "$LEGACY_NFS_SUBDIR"
test -n "$LEGACY_NFS_VOLUME_HANDLE"
test -n "$LEGACY_STORAGE_SIZE"
If the storage backend is not NFS CSI, do not reuse this manifest. Ask the storage administrator to provide a PV/PVC that mounts the same legacy data and keep the PVC name aligned with Config/cluster.spec.storage.pvc.claim.
Before replacing Registry v2 storage, stop the Registry v2 runtime and wait for the data-plane deployments to be removed:
ac patch config.imageregistry.operator.alauda.io cluster \
--type merge \
-p '{"spec":{"managementState":"Removed"}}'
while ac get deployment image-registry -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
while ac get deployment image-api-server -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
Create a static PV/PVC that points to the legacy data:
cat > "$MIGRATION_DIR/modern-registry-reuse-legacy-storage.yaml" <<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
name: $REUSE_PV_NAME
spec:
capacity:
storage: $LEGACY_STORAGE_SIZE
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- hard
- nfsvers=4.1
csi:
driver: nfs.csi.k8s.io
volumeHandle: $LEGACY_NFS_VOLUME_HANDLE
volumeAttributes:
server: $LEGACY_NFS_SERVER
share: $LEGACY_NFS_SHARE
subdir: $LEGACY_NFS_SUBDIR
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: $MODERN_REGISTRY_PVC
namespace: $MODERN_REGISTRY_NAMESPACE
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: $LEGACY_STORAGE_SIZE
storageClassName: ""
volumeName: $REUSE_PV_NAME
EOF
ac apply -f "$MIGRATION_DIR/modern-registry-reuse-legacy-storage.yaml"
Restart Registry v2 with unmanaged PVC storage and wait for both the Registry and Image API server:
ac patch config.imageregistry.operator.alauda.io cluster \
--type merge \
-p '{"spec":{"managementState":"Managed","storage":{"managementState":"Unmanaged","pvc":{"claim":"'"$MODERN_REGISTRY_PVC"'"}}}}'
while ! ac get deployment image-registry -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
while ! ac get deployment image-api-server -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
ac rollout status deployment/image-registry -n "$MODERN_REGISTRY_NAMESPACE" --timeout=300s
ac rollout status deployment/image-api-server -n "$MODERN_REGISTRY_NAMESPACE" --timeout=300s
Object Storage Reuse
For object storage such as S3, OSS, GCS, Azure, Swift, or IBM COS, configure Registry v2 to use the same bucket or container, endpoint, region, path prefix, credentials, CA, and access parameters that the legacy Registry used.
If the legacy Registry uses a path prefix and Registry v2 cannot configure the same prefix, use Scenario B instead. Also use Scenario B if the Registry v2 Operator does not support the legacy object storage type or a required access parameter.
If Registry v2 already uses another storage backend, stop the runtime before changing object storage:
ac patch config.imageregistry.operator.alauda.io cluster \
--type merge \
-p '{"spec":{"managementState":"Removed"}}'
while ac get deployment image-registry -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
while ac get deployment image-api-server -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
Confirm the supported storage fields in the current Operator version:
ac explain configs.imageregistry.operator.alauda.io.spec.storage --recursive
Then inspect the legacy Registry configuration and secrets:
ac get deployment image-registry -n "$LEGACY_REGISTRY_NAMESPACE" -o yaml
ac get configmap image-registry-config -n "$LEGACY_REGISTRY_NAMESPACE" -o yaml
ac get secret -n "$LEGACY_REGISTRY_NAMESPACE"
The following S3 structure is an example only. Use ac explain and the actual legacy Registry configuration as the source of truth, replace bucket, region, endpoint, CA, and related access settings, and remove fields that the current environment does not use:
apiVersion: imageregistry.operator.alauda.io/v1
kind: Config
metadata:
name: cluster
spec:
managementState: Managed
storage:
managementState: Unmanaged
s3:
bucket: <same-bucket>
region: <same-region>
regionEndpoint: <same-endpoint>
virtualHostedStyle: false
trustedCA:
name: <trusted-ca-configmap>
Save the storage configuration and apply it:
ac apply -f "$MIGRATION_DIR/modern-registry-object-storage.yaml"
while ! ac get deployment image-registry -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
while ! ac get deployment image-api-server -n "$MODERN_REGISTRY_NAMESPACE" >/dev/null 2>&1; do
sleep 5
done
Do not store object storage credentials in the migration environment file. Create or reuse the Secret required by the Registry v2 Operator, then verify that the Registry pod has picked up the storage settings:
ac get deployment image-registry -n "$MODERN_REGISTRY_NAMESPACE" -o yaml
ac logs -n "$MODERN_REGISTRY_NAMESPACE" deployment/image-registry -c registry --tail=80
ac rollout status deployment/image-registry -n "$MODERN_REGISTRY_NAMESPACE" --timeout=300s
ac rollout status deployment/image-api-server -n "$MODERN_REGISTRY_NAMESPACE" --timeout=300s
After storage reuse is configured, backfill metadata by inspecting the legacy Registry:
export METADATA_INSPECT_REGISTRY_HOST="$LEGACY_REGISTRY_HOST"
ac config set-registry-mode modern
backfill_metadata
If ac image info "$TARGET_REGISTRY_HOST/$image" fails before metadata backfill, that does not necessarily mean storage reuse is broken. Registry v2 can return NAME_UNKNOWN when Image and ImageStream metadata do not exist yet. Use the backfill_metadata result and the verification steps below as the decision point.
Scenario B: Copy Images Between Registries
Use this path when the source and target registries do not share storage.
Generate a mirror mapping file. This preserves namespace, repository, and tag names and only replaces the Registry host:
while read -r image; do
[ -n "$image" ] || continue
printf '%s/%s=%s/%s\n' \
"$LEGACY_REGISTRY_HOST" "$image" \
"$TARGET_REGISTRY_HOST" "$image"
done < "$IMAGE_LIST_FILE" > "$MAPPING_FILE"
sed -n '1,20p' "$MAPPING_FILE"
wc -l "$MAPPING_FILE"
Run a dry run before copying:
MIRROR_DRY_RUN_LOG="$MIGRATION_DIR/mirror-dry-run.log"
ac image mirror \
-f "$MAPPING_FILE" \
--dry-run \
--keep-manifest-list \
$REGISTRY_INSECURE_FLAG \
> "$MIRROR_DRY_RUN_LOG" 2>&1
echo "mirror dry-run log: $MIRROR_DRY_RUN_LOG"
sed -n '1,20p' "$MIRROR_DRY_RUN_LOG"
wc -l "$MIRROR_DRY_RUN_LOG"
After reviewing the plan, copy the images:
MIRROR_COPY_LOG="$MIGRATION_DIR/mirror-copy.log"
ac image mirror \
-f "$MAPPING_FILE" \
--continue-on-error \
--keep-manifest-list \
--max-per-registry 4 \
$REGISTRY_INSECURE_FLAG \
> "$MIRROR_COPY_LOG" 2>&1
copied_count="$(grep -c '^Copied ' "$MIRROR_COPY_LOG" || true)"
warning_count="$(grep -Eci 'error|failed|denied|unauthorized|forbidden' "$MIRROR_COPY_LOG" || true)"
echo "mirror copy summary: copied=$copied_count warning_lines=$warning_count log=$MIRROR_COPY_LOG"
if [ "$warning_count" -gt 0 ]; then
grep -Ein 'error|failed|denied|unauthorized|forbidden' "$MIRROR_COPY_LOG" | sed -n '1,20p'
fi
Keep --keep-manifest-list during migration to preserve manifest lists and multi-architecture images.
After the image copy finishes, switch to modern mode and backfill metadata by inspecting Registry v2:
export METADATA_INSPECT_REGISTRY_HOST="$TARGET_REGISTRY_HOST"
ac config set-registry-mode modern
backfill_metadata
Rerun Safely
Migration steps are designed to be rerunnable. If the current ImageStreamTag already points to the expected digest and target Registry reference, backfill_metadata skips creating a new ImageStreamMapping and counts the item as skipped.
For storage reuse, rerun metadata backfill:
export METADATA_INSPECT_REGISTRY_HOST="$LEGACY_REGISTRY_HOST"
ac config set-registry-mode modern
backfill_metadata
For cross-registry sync, rerun image copy and metadata backfill:
MIRROR_COPY_LOG="$MIGRATION_DIR/mirror-rerun.log"
ac image mirror \
-f "$MAPPING_FILE" \
--continue-on-error \
--keep-manifest-list \
--max-per-registry 4 \
$REGISTRY_INSECURE_FLAG \
> "$MIRROR_COPY_LOG" 2>&1
copied_count="$(grep -c '^Copied ' "$MIRROR_COPY_LOG" || true)"
warning_count="$(grep -Eci 'error|failed|denied|unauthorized|forbidden' "$MIRROR_COPY_LOG" || true)"
echo "mirror rerun summary: copied=$copied_count warning_lines=$warning_count log=$MIRROR_COPY_LOG"
if [ "$warning_count" -gt 0 ]; then
grep -Ein 'error|failed|denied|unauthorized|forbidden' "$MIRROR_COPY_LOG" | sed -n '1,20p'
fi
export METADATA_INSPECT_REGISTRY_HOST="$TARGET_REGISTRY_HOST"
backfill_metadata
After a rerun, confirm that the same tag still points to the expected digest and target Registry reference.
Verify Migration
Verify important business images and random samples. The following example selects the first image from the migration list:
VERIFY_IMAGE="$(awk 'NF > 0 { print $1; exit }' "$IMAGE_LIST_FILE")"
test -n "$VERIFY_IMAGE"
VERIFY_NAMESPACE="${VERIFY_IMAGE%%/*}"
VERIFY_REST="${VERIFY_IMAGE#*/}"
VERIFY_STREAM="${VERIFY_REST%%:*}"
VERIFY_TAG="${VERIFY_REST##*:}"
echo "verify image: $VERIFY_IMAGE"
ac get imagestreamtags "$VERIFY_STREAM:$VERIFY_TAG" -n "$VERIFY_NAMESPACE"
VERIFY_REF="$(
ac get imagestreamtags "$VERIFY_STREAM:$VERIFY_TAG" -n "$VERIFY_NAMESPACE" \
| awk 'NR > 1 && $3 == "current" { print $6; exit }'
)"
ac image info "$VERIFY_REF" $REGISTRY_INSECURE_FLAG
VERIFY_DIGEST="${VERIFY_REF##*@}"
VERIFY_IMAGE_NAME="$(printf '%s\n' "$VERIFY_DIGEST" | sed 's/:/-/g')"
VERIFY_IMAGES_OUTPUT="$MIGRATION_DIR/verify-modern-images.txt"
ac get images > "$VERIFY_IMAGES_OUTPUT"
awk -v digest="$VERIFY_DIGEST" -v safe="$VERIFY_IMAGE_NAME" '
NR > 1 && ($1 == digest || $1 == safe || index($2, digest) > 0) { found = 1 }
END { exit found ? 0 : 1 }
' "$VERIFY_IMAGES_OUTPUT"
Acceptance criteria:
- Registry v2 can inspect or pull the current digest reference from
ImageStreamTag.
ImageStreamMapping creation succeeded.
- Reruns do not produce unexplained failures.
ImageStreamTag points to the expected digest and Registry v2 address.
ac get images in modern mode shows the migrated Image resources with OCP-style image semantics.
- Sample images in critical business namespaces can be read.
Switch Registry Mode
After verification, switch the current kubeconfig context to modern Registry mode:
ac config set-registry-mode modern
ac config get-registry-mode
ac config set-registry-mode changes only the current context's cluster by default. Use --all-clusters only after every cluster in the ACP session has completed Registry migration.
Run dry-run image management commands to confirm that the backend has switched to Registry v2:
ac get images
ac delete images --repo "$VERIFY_IMAGE"
ac adm prune images
ac delete images and ac adm prune images are dry-run by default. Add --confirm only after reviewing the output.
Roll Back
If workloads cannot read business images after cutover, switch the affected context back to legacy mode:
ac config set-registry-mode legacy
ac config get-registry-mode
Keep the following artifacts for troubleshooting and reruns:
- Image lists.
- Mirror mapping files.
ac image mirror output.
ac image info output.
- Generated
ImageStreamMapping YAML files.
ac create -f output.
- Migration verification output.
After the issue is fixed, continue from the image list. You do not need to rebuild the migration scope from the beginning.