diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3b9ff39bf269abffc0331185c88aa2cb38a37616..e8d010bd2e12dff9c16febab4aa9725052bfd2e3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -43,11 +43,10 @@ backup:
     - >-
       docker run
       --volume /var/backups/registry:/var/backups/registry
-      --volume /run/docker.sock:/run/docker.sock
+      --user nobody
+      --rm
       registry.shore.co.il/registry-backup
       backup registry.shore.co.il /var/backups/registry
-  after_script:
-    - docker image prune -f
   retry:
     max: 2
   timeout: 3h
diff --git a/backup/.dockerignore b/backup/.dockerignore
index ff99b532cd160e0d083867a08d8842b64bb138a8..5664021a5516007889500c3fb8d99da128793e9a 100644
--- a/backup/.dockerignore
+++ b/backup/.dockerignore
@@ -1,2 +1,3 @@
 *
 !backup
+!restore
diff --git a/backup/Dockerfile b/backup/Dockerfile
index 7be4b236985ce4948597f6650460153e8255b32e..fc73888b364bd25805383452c423d6a2e53333a9 100644
--- a/backup/Dockerfile
+++ b/backup/Dockerfile
@@ -1,4 +1,10 @@
 FROM docker.io/library/alpine:3.14
 # hadolint ignore=DL3018
-RUN apk add --update --no-cache docker-py
+RUN echo 'https://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories && \
+    echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \
+    apk add --update --no-cache \
+        skopeo \
+        reg \
+    ;
 COPY --chown=root:root backup /usr/local/bin/backup
+COPY --chown=root:root restore /usr/local/bin/restore
diff --git a/backup/backup b/backup/backup
index 83c0ea731e9c50698aa275e2cc880980a65f8a4d..fb889fab80b3f585808b0a1c10bea4cee0b5f85a 100755
--- a/backup/backup
+++ b/backup/backup
@@ -1,74 +1,49 @@
-#!/usr/bin/env python3
-"""Backup a container image registry."""
-
-
-import argparse
-import pathlib
-import sys
-import docker
-import requests
-
-
-def get_images(registry):
-    """Return a list of images from the registry."""
-    return requests.get(f"https://{registry}/v2/_catalog").json()[
-        "repositories"
-    ]
-
-
-def get_image_tags(registry, image):
-    """Return a list of tags for an image in the registry."""
-    return requests.get(f"https://{registry}/v2/{image}/tags/list").json()[
-        "tags"
-    ]
-
-
-def backup(docker_client, registry, image, tag, dest):
-    """Backup an image tag from the registry to a file in the destination
-    directory."""
-    full_name = f"{registry}/{image}:{tag}"
-    print(f"Backing up {full_name}.", file=sys.stderr)
-    try:
-        docker_client.images.get(full_name)
-        image_existed = True
-    except docker.errors.ImageNotFound:
-        image_existed = False
-    docker_image = docker_client.images.pull(full_name)
-    with open(dest / f"{tag}.tar", "wb") as tarball:
-        for chunk in docker_image.save():
-            tarball.write(chunk)
-    if not image_existed:
-        docker_client.images.remove(full_name)
-
-
-def backup_registry(registry, dest):
-    """Backup the images in the registry to the destination."""
-    docker_client = docker.from_env(timeout=600)
-    docker_client.ping()
-    for image in get_images(registry):
-        (dest / image).mkdir(exist_ok=True)
-        for tag in get_image_tags(registry, image):
-            backup(docker_client, registry, image, tag, dest / image)
-
-
-if __name__ == "__main__":
-    arg_parser = argparse.ArgumentParser(description=__doc__)
-    arg_parser.add_argument("registry", help="FQDN of the registry to backup.")
-    arg_parser.add_argument(
-        "destination", help="Location to store the images.", type=pathlib.Path
-    )
-    args = arg_parser.parse_args()
-    destination = args.destination.expanduser()
-    if not destination.exists() or not destination.is_dir():
-        arg_parser.error(
-            "Backup destination doesn't exists or isn't a directory."
-        )
-    try:
-        destination.touch()
-    except IsADirectoryError:
-        arg_parser.error("Can't write to the backup destination.")
-    try:
-        backup_registry(args.registry, destination)
-    except Exception as exception:
-        arg_parser.error(str(exception))
-    sys.exit()
+#!/bin/sh
+set -eu
+
+usage() {
+    echo "$0: REGISTRY_DOMAIN BACKUP_DEST"
+}
+
+if [ "${1:-}" = -h ] || [ "${1:-}" = --help ]
+then
+    usage
+    exit 0
+fi
+
+if [ "$#" -ne 2 ]
+then
+    usage
+    exit 1
+fi
+
+registry="$1"
+dest="$2"
+
+mkdir -p "$dest"
+
+reg ls "$registry" | \
+   sed 's/,//g' | \
+   awk -v "registry=$registry" -v "dest=$dest" '
+BEGIN {
+    exitcode = 0
+}
+NR>2 {
+    system("mkdir -p " dest "/" $1)
+    for (i=2; i<=NF; i++) {
+        image_url = registry "/" $1 ":" $(i)
+        image_file = dest "/" $1 "/" $(i) ".tar"
+        printf "Saving %s to %s.\n", image_url, image_file
+        system("rm " image_file)
+        if (system("skopeo copy docker://" image_url " docker-archive://" image_file) == 0)
+            printf "Backup of %s was successful.\n", image_url
+        else {
+            exitcode = 1
+            printf "Backup of %s failed, continuing with other images.\n", image_url
+        }
+    }
+}
+END {
+    exit retruncode
+}
+'
diff --git a/backup/restore b/backup/restore
new file mode 100755
index 0000000000000000000000000000000000000000..1a2485251c33a70432394c93fb89330ef214bfc9
--- /dev/null
+++ b/backup/restore
@@ -0,0 +1 @@
+#!/bin/sh