Abstract
Modern IT infrastructures increasingly demand self-hosted solutions that can ensure rapid, secure, and reliable deployment of containerized applications. In response, this work presents a robust and automated system for mirroring Docker images from public repositories to a local registry. The proposed solution tackles the challenges of version discrepancies, image integrity validation, and fault tolerance through a modular architecture that combines multiple Python and shell scripts with standard DevOps tools.
At the heart of the system are three specialized scripts: one to verify if the locally mirrored image is up-to-date by comparing digests of the source and mirror, a second to validate the integrity of an image by parsing and checking its manifest (accommodating both multi-architecture and single-architecture formats), and a third to delete any image that fails integrity checks or is outdated. These are orchestrated by a master shell script which manages the overall mirroring process. This script implements an exponential backoff strategy to gracefully handle transient network or API errors and employs conditional logic to decide whether to force a re-copy of the image when standard procedures do not suffice.
The system’s design addresses several key obstacles: ensuring synchronization consistency between source and mirror, executing precise integrity checks using Docker manifest APIs, and maintaining registry hygiene by cleaning up inconsistent states. Its modular nature not only simplifies maintenance and testing but also enables seamless integration with container orchestration frameworks and periodic scheduling via cron jobs. Consequently, the solution provides a scalable blueprint for local image management in environments where external dependencies must be minimized.
Experimental deployment scenarios demonstrate that the system's rigorous error handling, coupled with its automated cleanup and re-mirroring capabilities, can significantly enhance reliability and performance in production-like settings. This work thereby offers valuable insights into constructing a self-healing, automated image mirroring framework that balances operational efficiency with robust fault tolerance, serving as a practical guideline for advanced DevOps implementations.
Before starting, make sure you have a docker registry server running locally:
Host Registry
Registry is a self-hosted docker registry that allows you to store and manage your docker images. It is similar to Docker Hub, but it is self-hosted and can be used for private repositories.
To host Registry on AnduinOS, run the following commands.
First, make sure Docker is installed on your machine. If not, you can install Docker by running the following commands:
curl -fsSL get.docker.com -o get-docker.sh
CHANNEL=stable sh get-docker.sh
rm get-docker.sh
Create a new folder to save the service configuration files:
# Please install Docker first
mkdir -p ~/Source/ServiceConfigs/Registry
cd ~/Source/ServiceConfigs/Registry
Make sure no other process is taking 6157 port on your machine.
function port_exist_check() {
if [[ 0 -eq $(sudo lsof -i:"$1" | grep -i -c "listen") ]]; then
echo "$1 is not in use"
sleep 1
else
echo "Warning: $1 is occupied"
sudo lsof -i:"$1"
echo "Will kill the occupied process in 5s"
sleep 5
sudo lsof -i:"$1" | awk '{print $2}' | grep -v "PID" | sudo xargs kill -9
echo "Killed the occupied process"
sleep 1
fi
}
port_exist_check 8080
Then, create a docker-compose.yml
file with the following content:
cat << EOF > ./docker-compose.yml
version: '3.9'
services:
registry-ui:
depends_on:
- registry-server
image: joxit/docker-registry-ui:main
ports:
- target: 80
published: 8080
protocol: tcp
mode: host
networks:
- net
environment:
- SINGLE_REGISTRY=true
- REGISTRY_TITLE=My Registry
- DELETE_IMAGES=true
- SHOW_CONTENT_DIGEST=true
- NGINX_PROXY_PASS_URL=http://registry-server:5000
- SHOW_CATALOG_NB_TAGS=true
- CATALOG_MIN_BRANCHES=1
- CATALOG_MAX_BRANCHES=1
- TAGLIST_PAGE_SIZE=100
- REGISTRY_SECURED=false
- CATALOG_ELEMENTS_LIMIT=1000
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
update_config:
order: start-first
registry-server:
image: registry
volumes:
- registry-data:/var/lib/registry
networks:
- net
environment:
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '[https://hub.aiursoft.cn]' # Replace with your domain
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '[true]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'
REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
update_config:
order: start-first
registry-cleaner:
depends_on:
- registry-server
image: alpine:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
entrypoint:
- "/bin/sh"
- "-c"
- |
echo "Installing docker";
apk add --no-cache docker;
echo "Starting registry cleaner";
sleep 20;
docker ps --filter name=registry-server -q | xargs -r -I {} docker exec {} registry garbage-collect /etc/docker/registry/config.yml --delete-untagged=true;
echo "Garbage collection done. Sleeping infinitely.";
sleep infinity;
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
update_config:
order: start-first
volumes:
registry-data:
driver: local
driver_opts:
type: none
o: bind
device: /swarm-vol/registry-data
networks:
net:
driver: overlay
EOF
sudo mkdir -p /swarm-vol/registry-data
Then, deploy the service:
sudo docker swarm init --advertise-addr $(hostname -I | awk '{print $1}')
sudo docker stack deploy -c docker-compose.yml registry --detach
That's it! You have successfully hosted registry on AnduinOS.
You can access registry by visiting http://localhost:8080
in your browser.
Uninstall
To uninstall Registry, run the following commands:
sudo docker stack rm registry
sleep 20 # Wait for the stack to be removed
sudo docker system prune -a --volumes -f # Clean up used volumes and images
To also remove the data, log, and config files, run the following commands:
sudo rm /swarm-vol/registry-data -rf
That's it! You have successfully uninstalled Registry from AnduinOS.
Build mirror script
We need to install regctl to mirror the images. Regctl is a command-line tool for managing Docker images and registries.
curl -L https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 >regctl && \
chmod 755 regctl && \
mv regctl /usr/local/bin/
And we need 3 python scripts:
- Check if the image is already latest.
- Check if the integrity of the image is correct.
- Delete an image if it is not the latest or the integrity is incorrect.
Check if the image is already latest
Please name the script is_latest.py
and save it in the same directory as the other scripts.
#!/usr/bin/env python3
import sys
import subprocess
import os
def get_mirror_image(source_image):
"""Convert a source image name to its mirror equivalent."""
if ':' in source_image:
repo_part, tag = source_image.rsplit(':', 1)
else:
repo_part = source_image
tag = "latest"
# Handle special cases for Docker Hub
if '/' not in repo_part:
# Simple Docker Hub image like "nginx"
mirror_repo = f"{repo_part}"
else:
# Keep as is for other registries or namespaced Docker Hub images
mirror_repo = repo_part
## REPLACE THIS WITH YOUR MIRROR DOMAIN!!!
return f"hub.aiursoft.cn/{mirror_repo}:{tag}"
def get_image_digest(image):
"""Get the digest for a Docker image using regctl."""
try:
result = subprocess.run(
["regctl", "image", "digest", image],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=False
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
else:
print(f"Failed to get digest for {image}: {result.stderr}", file=sys.stderr)
return None
except Exception as e:
print(f"Error running regctl for {image}: {e}", file=sys.stderr)
return None
def image_exists(image):
"""Check if an image exists using regctl."""
try:
result = subprocess.run(
["regctl", "image", "manifest", image],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False
)
return result.returncode == 0
except:
return False
def main():
if len(sys.argv) != 2:
print("Usage: python is_latest.py <source-image>", file=sys.stderr)
sys.exit(2)
source_image = sys.argv[1]
mirror_image = get_mirror_image(source_image)
print(f"Checking if {source_image} is up to date in mirror...", file=sys.stderr)
print(f"Source: {source_image}", file=sys.stderr)
print(f"Mirror: {mirror_image}", file=sys.stderr)
# First check if mirror image exists
if not image_exists(mirror_image):
print(f"Mirror image {mirror_image} doesn't exist", file=sys.stderr)
sys.exit(1) # Not latest - mirror doesn't exist
# Get digests
source_digest = get_image_digest(source_image)
mirror_digest = get_image_digest(mirror_image)
if not source_digest or not mirror_digest:
print("Failed to get digests for comparison", file=sys.stderr)
sys.exit(1) # Error, assume not latest
print(f"Source digest: {source_digest}", file=sys.stderr)
print(f"Mirror digest: {mirror_digest}", file=sys.stderr)
# Compare digests
if source_digest == mirror_digest:
print(f"✓ Image {source_image} is up to date in the mirror", file=sys.stderr)
sys.exit(0) # True - is latest
else:
print(f"✗ Image {source_image} needs to be updated in the mirror", file=sys.stderr)
sys.exit(1) # False - not latest
if __name__ == '__main__':
main()
Check if the integrity of the image is correct
Please name the script check.py
and save it in the same directory as the other scripts.
#!/usr/bin/env python3
import sys
import requests
def check_image(image):
"""
Checking if the image is a valid Aiursoft registry image.
image format should be:hub.aiursoft.cn/<repository>:<tag>
"""
### REPLACE THIS WITH YOUR MIRROR DOMAIN!!!
registry = "hub.aiursoft.cn"
if not image.startswith(f"{registry}/"):
print("Image doesn't belong to registry", file=sys.stderr)
return False
try:
rest = image[len(f"{registry}/"):]
repository, tag = rest.rsplit(":", 1)
except ValueError:
print("Image format error", file=sys.stderr)
return False
# Use the tag to get the manifest
url_tag = f"https://{registry}/v2/{repository}/manifests/{tag}"
resp = requests.get(url_tag, headers={
'Accept': 'application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
})
if not resp.ok:
print(f"{url_tag} Failed to get manifest: {repository}:{tag}. Status Code: {resp.status_code}", file=sys.stderr)
return False
try:
data = resp.json()
except Exception as e:
print("Failed to parse manifest data", file=sys.stderr)
return False
# If response contains "manifests" field, it means the image is a multi-arch image
if "manifests" in data:
try:
digest = data["manifests"][0]["digest"]
except (IndexError, KeyError):
print("Failed to get digest from manifests", file=sys.stderr)
return False
url_digest = f"https://{registry}/v2/{repository}/manifests/{digest}"
resp_digest = requests.get(url_digest, headers={
'Accept': 'application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
})
if not resp_digest.ok:
print(f"{url_digest} Failed to get manifest: {repository}:{tag}. Status Code: {resp_digest.status_code}", file=sys.stderr)
return False
if "manifests" in data:
print(f"The manifest data contains the 'manifests' field, indicating that this image is a multi-arch image.", file=sys.stderr)
return True
elif "layers" in data:
print(f"The manifest data does not contain the 'manifests' field but contains the 'layers' field, indicating that this image is a single-arch image.", file=sys.stderr)
return True
else:
print(f"The manifest data does not contain 'manifests' and 'layers' fields. URL: {url_tag}. This means something might be wrong!", file=sys.stderr)
return False
def main():
if len(sys.argv) < 2:
print("Usage: check.py <image>", file=sys.stderr)
sys.exit(2)
image = sys.argv[1]
if check_image(image):
print(f"Image {image} is valid", file=sys.stderr)
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
main()
Delete an image if it is not the latest or the integrity is incorrect
Please name the script delete.py
and save it in the same directory as the other scripts.
#!/usr/bin/env python3
import sys
import requests
def parse_image(image):
"""
This function parses the image string to extract the domain, repository, and tag.
"""
if ':' in image:
image_part, tag = image.rsplit(':', 1)
else:
image_part = image
tag = "latest"
if '/' not in image_part:
print("Error: Image name must be in the format <domain>/<repo>:<tag>")
sys.exit(1)
parts = image_part.split('/')
domain = parts[0]
repository = '/'.join(parts[1:])
return domain, repository, tag
def get_digest(domain, repository, tag):
"""
Call the GET interface to get the manifest of the image.
"""
url = f"http://{domain}/v2/{repository}/manifests/{tag}"
headers = {'Accept': 'application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'}
print(f"Fetching manifest from {url} ...")
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"Error: Unable to fetch manifest. Status code: {response.status_code}")
print("The image may not exist in the registry.")
sys.exit(1)
digest = response.headers.get('Docker-Content-Digest')
if not digest:
print("Error: Docker-Content-Digest header not found in response.")
sys.exit(1)
return digest
def delete_manifest(domain, repository, digest):
"""
Call the DELETE interface to delete the manifest.
"""
url = f"http://{domain}/v2/{repository}/manifests/{digest}"
print(f"Deleting manifest at {url} ...")
response = requests.delete(url, headers={
'Accept': 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json'
})
if response.status_code == 202:
print("Deletion successful.")
else:
print(f"Error: Deletion failed with status code: {response.status_code}")
print(response.text)
sys.exit(1)
def main():
if len(sys.argv) != 2:
print("Usage: python ./delete.py <domain>/<repo>:<tag>")
sys.exit(1)
image = sys.argv[1]
domain, repository, tag = parse_image(image)
print(f"Processing image: {domain}/{repository}:{tag}")
digest = get_digest(domain, repository, tag)
print(f"Found digest: {digest}")
delete_manifest(domain, repository, digest)
if __name__ == '__main__':
main()
Mirror the image
Please name the script mirror.sh
and save it in the same directory as the other scripts.
#!/bin/bash
set -e
try_docker_login() {
# Load DOCKER_USERNAME from environment variable
# Load DOCKER_PASSWORD from /run/secrets/docker-password
# If any of those not set, do not login
echo "Attempting to login to Docker Hub..."
echo "Docker USERNAME: $DOCKER_USERNAME"
DOCKER_PASSWORD=$(cat /run/secrets/DOCKER_PASSWORD 2>/dev/null || echo "")
if [[ -z "$DOCKER_USERNAME" || -z "$DOCKER_PASSWORD" ]]; then
echo ">>> Docker credentials are not set. Skipping login."
return 0
fi
echo ">>> Docker credentials are set. Attempting login..."
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
}
actual_mirror_docker() {
sourceImage="$1"
attempt="$2"
if [[ "$sourceImage" != *":"* ]]; then
sourceImage="${sourceImage}:latest"
fi
imageName=$(echo "$sourceImage" | cut -d: -f1)
imageTag=$(echo "$sourceImage" | cut -d: -f2)
# REPLACE THIS WITH YOUR MIRROR DOMAIN!!!
finalMirror="hub.aiursoft.cn/${imageName}:${imageTag}"
echo ">>> Checking if $sourceImage is already mirrored to $finalMirror"
if python3 is_latest.py "$sourceImage"; then
echo ">>> Image $sourceImage is already mirrored to $finalMirror. Will check integrity..."
if ! python3 check.py "$finalMirror"; then
echo ">>> Integrity check failed for $finalMirror. Attempting to delete..."
python3 delete.py "$finalMirror"
return 1
fi
echo ">>> Image $finalMirror is valid. Skipping..."
return 0
fi
# If first attempt, use
if [[ $attempt -eq 1 ]]; then
regctl image copy "$sourceImage" "$finalMirror" --digest-tags
else
# If second or more attempts, use --force-recursive
regctl image copy "$sourceImage" "$finalMirror" --force-recursive --digest-tags
fi
sleep 3
echo ">>> Image $sourceImage copied to $finalMirror. Checking integrity..."
# First check - manifest via regctl
if ! regctl image manifest "$finalMirror" &> /dev/null; then
echo ">>> Manifest check failed for $finalMirror. Attempting to delete..."
python3 delete.py "$finalMirror"
return 1
fi
if ! python3 check.py "$finalMirror"; then
echo ">>> Health check failed for $finalMirror. Attempting to delete..."
python3 delete.py "$finalMirror"
return 1
fi
echo ">>> Image $finalMirror is valid. Proceeding to push..."
return 0
}
mirror_docker() {
sourceImage="$1"
max_attempts=8
for attempt in $(seq 1 $max_attempts); do
echo ">>> Attempting $attempt/$max_attempts: $sourceImage"
if actual_mirror_docker "$sourceImage" "$attempt"; then
echo ">>> Image $sourceImage mirrored successfully."
return 0
fi
# Calculate backoff time with exponential increase and some randomness
backoff=$((300 + (attempt * attempt * 15) + (RANDOM % 30)))
if [ $attempt -lt $max_attempts ]; then
echo ">>> Image $sourceImage failed to mirror. Retrying in $backoff seconds..."
sleep $backoff
else
echo ">>> Image $sourceImage failed to mirror after $max_attempts attempts. Skipping..."
return 1
fi
done
}
try_docker_login
mirror_docker "24oi/oi-wiki"
mirror_docker "alpine"
mirror_docker "andyzhangx/samba:win-fix"
mirror_docker "artalk/artalk-go"
mirror_docker "bitwardenrs/server"
mirror_docker "busybox"
mirror_docker "bytemark/webdav"
mirror_docker "caddy"
mirror_docker "caddy:builder"
mirror_docker "clickhouse/clickhouse-server"
mirror_docker "collabora/code"
mirror_docker "consul:1.15.4"
mirror_docker "couchdb"
mirror_docker "couchdb:2.3.0"
mirror_docker "debian:12"
mirror_docker "debian:12.4"
mirror_docker "debian:stable-slim"
mirror_docker "dockurr/windows"
mirror_docker "dperson/samba"
mirror_docker "edgeneko/neko-image-gallery:edge-cpu"
mirror_docker "edgeneko/neko-image-gallery:edge-cuda"
mirror_docker "elasticsearch:8.11.3"
mirror_docker "filebrowser/filebrowser"
mirror_docker "frolvlad/alpine-gxx"
mirror_docker "gcc"
mirror_docker "gcc:4.9.4"
mirror_docker "ghcr.io/anduin2017/how-to-cook:latest"
mirror_docker "ghcr.io/open-webui/open-webui:main"
mirror_docker "ghcr.io/thomiceli/opengist"
mirror_docker "ghcr.io/usememos/memos"
mirror_docker "gitea/gitea"
mirror_docker "gitlab/gitlab-ce"
mirror_docker "gitlab/gitlab-ee"
mirror_docker "golang"
mirror_docker "golang:1.21.5"
mirror_docker "grafana/grafana"
mirror_docker "haproxy"
mirror_docker "haskell"
mirror_docker "haskell:9.8.1"
mirror_docker "hello-world"
mirror_docker "homeassistant/home-assistant"
mirror_docker "httpd"
mirror_docker "immybot/remotely"
mirror_docker "immybot/remotely:69"
mirror_docker "immybot/remotely:88"
mirror_docker "imolein/lua:5.4"
mirror_docker "influxdb"
mirror_docker "influxdb:1.8"
mirror_docker "indexyz/mikutap"
mirror_docker "jellyfin/jellyfin"
mirror_docker "jgraph/drawio:24.7.17"
mirror_docker "joxit/docker-registry-ui"
mirror_docker "jvmilazz0/kavita"
mirror_docker "loicsharma/baget"
mirror_docker "louislam/uptime-kuma"
mirror_docker "mariadb"
mirror_docker "mcr.microsoft.com/azuredataexplorer/kustainer-linux"
mirror_docker "mcr.microsoft.com/dotnet/aspnet:6.0"
mirror_docker "mcr.microsoft.com/dotnet/aspnet:7.0"
mirror_docker "mcr.microsoft.com/dotnet/aspnet:8.0"
mirror_docker "mcr.microsoft.com/dotnet/aspnet:9.0"
mirror_docker "mcr.microsoft.com/dotnet/sdk:6.0"
mirror_docker "mcr.microsoft.com/dotnet/sdk:7.0"
mirror_docker "mcr.microsoft.com/dotnet/sdk:8.0"
mirror_docker "mcr.microsoft.com/dotnet/sdk:9.0"
mirror_docker "mcr.microsoft.com/mssql/server"
mirror_docker "mcr.microsoft.com/powershell"
mirror_docker "mediawiki"
mirror_docker "mediacms/mediacms"
mirror_docker "memcached"
mirror_docker "mongo"
mirror_docker "mysql"
mirror_docker "nextcloud"
mirror_docker "nextcloud:27.1.0"
mirror_docker "nextcloud:production"
mirror_docker "nextcloud:stable"
mirror_docker "nginx"
mirror_docker "nginx:alpine"
mirror_docker "node"
mirror_docker "node:16-alpine"
mirror_docker "node:21-alpine"
mirror_docker "nvidia/cuda:11.6.2-base-ubuntu20.04"
mirror_docker "nvidia/cuda:11.8.0-devel-ubuntu22.04"
mirror_docker "ollama/ollama"
mirror_docker "openjdk:23-jdk"
mirror_docker "openjdk:8-jdk"
mirror_docker "oven/bun:slim"
mirror_docker "owncast/owncast"
mirror_docker "passivelemon/terraria-docker"
mirror_docker "perl:5.39.5"
mirror_docker "phanan/koel"
mirror_docker "php:8.3.0-zts"
mirror_docker "portainer/portainer-ce"
mirror_docker "postgres"
mirror_docker "postgres:14-alpine"
mirror_docker "postgres:15.2-alpine"
mirror_docker "prom/prometheus"
mirror_docker "pytorch/pytorch:2.3.0-cuda11.8-cudnn8-devel"
mirror_docker "python:3.10"
mirror_docker "python:3.11"
mirror_docker "qdrant/qdrant"
mirror_docker "rabbitmq"
mirror_docker "redis"
mirror_docker "redis:7-alpine"
mirror_docker "redis:alpine"
mirror_docker "registry:2"
mirror_docker "rigetti/lisp"
mirror_docker "ruby:3.2.2"
mirror_docker "rust"
mirror_docker "rust:1.81-slim"
mirror_docker "rust:slim"
mirror_docker "rustdesk/rustdesk-server:latest"
mirror_docker "sameersbn/apt-cacher-ng"
mirror_docker "snowdreamtech/frpc"
mirror_docker "snowdreamtech/frps"
mirror_docker "swarmpit/agent"
mirror_docker "swarmpit/swarmpit"
mirror_docker "swift:5.8.1"
mirror_docker "teddysun/xray"
mirror_docker "telegraf"
mirror_docker "thedaviddelta/lingva-translate"
mirror_docker "traefik"
mirror_docker "ubuntu:22.04"
mirror_docker "ubuntu:24.04"
mirror_docker "ubuntu:24.10"
mirror_docker "verdaccio/verdaccio"
mirror_docker "vminnovations/typescript-sdk:16-latest"
mirror_docker "wordpress:php8.3-fpm-alpine"
echo "All images are pulled and pushed to the mirror."
Run the script
Make sure you have python3 and requests
installed:
sudo apt-get install python3
pip3 install requests
Make sure the script is executable:
chmod +x mirror.sh
Then, run the script:
./mirror.sh
That's it! You have successfully mirrored the docker images to your local server.
Mirror in docker
You can also run the script in a docker container. Just make sure you have the docker.sock
mounted to the container.
FROM ubuntu-with-docker
WORKDIR /
RUN apt update && \
apt install -y cron python3 python-is-python3 python3-pip curl && \
rm -rf /var/lib/apt/lists/*
RUN curl -L https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 >regctl && \
chmod 755 regctl && \
mv regctl /usr/local/bin/
ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN pip install requests
# Copy entry script
COPY entry.sh /entry.sh
COPY check.py /check.py
COPY delete.py /delete.py
COPY is_latest.py /is_latest.py
RUN chmod +x /entry.sh
# Register a crontab job to run entry every day 3:00
RUN crontab -l | { cat; echo "0 3 * * * /entry.sh"; } | crontab -
# Run this job at the beginning with verbose output
ENTRYPOINT ["cron", "-f", "-L 15"]
And build the image:
docker build -t mirror .
Then, run the image:
docker run -d \
--name mirror \
--restart always \
--network host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /run/secrets:/run/secrets \
mirror
That's it! You have successfully mirrored the docker images to your local server in a docker container.
Using your registry
First, you need to tell docker that the registry might not support HTTPS. You can do this by adding the following line to your /etc/docker/daemon.json
file:
{
"insecure-registries": ["localhost:8080"]
}
Then, restart the docker service:
sudo systemctl restart docker
To use your registry, simply pull the image from your local registry:
docker pull localhost:8080/24oi/oi-wiki
You can also push images to your local registry:
docker tag 24oi/oi-wiki localhost:8080/24oi/oi-wiki
docker push localhost:8080/24oi/oi-wiki
Conclusion
In this guide, we have shown you how to mirror docker images to your local server using a self-hosted docker registry. We have also provided a script to automate the process of mirroring the images and checking their integrity.
这篇文章提供了一种有效的方法,通过脚本自动化将Docker镜像同步到本地服务器,并使用私有Registry进行管理。这种方案对于离线环境尤其有用,因为它确保了关键镜像的可用性,无需依赖外部仓库。
在实施过程中,确保通信安全是至关重要的。在生产环境中,最好启用HTTPS以保护数据传输,这可以通过配置Nginx等反向代理服务器并使用Let's Encrypt证书来实现。此外,对于容器运行时的资源管理,可以通过设置内存和CPU限制来防止资源耗尽。
关于错误处理,建议在脚本中加入检查逻辑,以便在拉取或推送镜像失败时记录日志并通知管理员。这样可以及时发现问题,并进行修复。此外,对于定期清理旧镜像,可以采用分层存储策略,定期删除过期的标签以释放空间。
最后,这种自动化方法能够显著提升效率,同时通过容器化部署Registry来简化维护。未来可以考虑集成更多功能,如镜像更新通知和多Registry支持,以进一步增强其灵活性和实用性。