What to replace Docker Hub with in Russia

Docker Hub was recently blocked in Russia. In this post, I will tell you how to live with it and how to quickly migrate current projects using Docker Hub.

1. Access to public images

The first thing to do is to regain the ability to do docker pull public images (for example, docker pull ubuntu:latest). For this we will use the mirror from Google: https://mirror.gcr.io/. There is also a mirror from Yandex cr.yandex/mirrorbut there seem to be few images there (ubuntu:latest – There is, python:3.12 — no). You can specify a mirror each time, for example docker pull mirror.gcr.io/ubuntu:latest. But it is more convenient to specify the mirror once in the Docker settings and do docker pull as usual: docker pull ubuntu:latest (the mirror will be used automatically).

I wrote a script that will do everything for you. You just need to pass the mirror as the first argument. Tested on Linux Ubuntu and MacOS.

Note for MacOS: there are sleeps in the code to wait for Docker to restart. They can be increased if necessary.

Note for Windows: it is enough to just correct the code where "$os" == "CYGWIN_NT" and it should work.

Script:

#!/usr/bin/env bash

set -e

DOCKER_REGISTRY_MIRROR="$1"

# utils__str_strip takes input from stdin and strips space characters
function utils__str_strip() {
    cat | tr -d '[:space:]'
}

# utils__is_true takes single argument and
# returns 0 if argument equals "true" (case-insensitive)
# returns 1 otherwise
function utils__is_true() {
    bool="$(echo "$1" | tr '[:upper:]' '[:lower:]' | utils__str_strip)"
    if [ "$bool" == "true" ]; then
        return 0
    fi
    return 1
}

function _get_docker_daemon_config_path() {
    os="$(uname -s)"
    local path
    if [ "$os" == "Linux" ]; then
        path="/etc/docker/daemon.json"
    elif [ "$os" == "Darwin" ]; then
        # path="~/.config/docker/daemon.json" # https://docs.docker.com/config/daemon/
        path="$HOME/.docker/daemon.json"
    elif [ "$os" == "CYGWIN_NT" ] || [ "$os" == "MINGW32_NT" ] || [ "$os" == "MSYS_NT" ]; then
        # NOTE: not tested
        # https://docs.docker.com/config/daemon/
        path="C:\ProgramData\docker\config\daemon.json"
    else
        echo "Error: unsupported operating system"
        exit 1
    fi
    echo $path
}

function _restart_docker() {
    os="$(uname -s)"
    local path
    if [ "$os" == "Linux" ]; then
        sudo systemctl restart docker
    elif [ "$os" == "Darwin" ]; then
        pkill 'Docker' || true
        sleep 3
        open -a Docker
        sleep 3
    elif [ "$os" == "CYGWIN_NT" ] || [ "$os" == "MINGW32_NT" ] || [ "$os" == "MSYS_NT" ]; then
        # NOTE: not tested
        # https://forums.docker.com/t/restart-docker-service-from-command-line/27331/3
        restart-service *docker*
    else
        echo "Error: unsupported operating system"
        exit 1
    fi
}

function dr__has_mirror() {
    local mirror="$1"
    sudo docker system info --format json | jq -r ".RegistryConfig.Mirrors | if index(\"${mirror}\") == null then \"false\" else \"true\" end"
}

function dr__add_mirror() {
    local mirror="$1"
    local config_path=$(_get_docker_daemon_config_path)
    (cat "$config_path" 2>/dev/null || echo "{}") | jq ". + {\"registry-mirrors\": [\"${mirror}\"]}" > /tmp/daemon.json && sudo mv /tmp/daemon.json "$config_path"
    _restart_docker
}

function DR_UPDATE_MIRROR() {
    mirror="$1"
    if ! utils__is_true $(dr__has_mirror "$mirror"); then
        echo "Target docker registry mirror ('$mirror') not found"
        echo "Start configuring docker registry mirror"
        dr__add_mirror "$mirror"
        if [ "$(dr__has_mirror "$mirror")" == "false" ]; then
            echo "Failed to configure docker registry mirror"
            exit 1;
        fi;
        echo "Successfully configured docker registry mirror"
    else
        echo "Mirror is already configured (look at "docker system info")"
    fi
}

DR_UPDATE_MIRROR "$DOCKER_REGISTRY_MIRROR"

Script logic:

  • Check if the mirror is already set up

  • If there is no mirror, then update the config

  • Restart docker

Classic way to start

  1. Create a file with a script update_mirror

  2. Renewing rights chmod +x update_mirror

  3. Let's launch ./update_mirror 'https://mirror.gcr.io/'

A method for the laziest 🙂

curl https://gist.githubusercontent.com/Deimvis/c747446725c84cf0731e82d76f7cc67b/raw/709e8fe296121fb1445fca49086ab9359ca5da67/update_mirror.sh | bash -s 'https://mirror.gcr.io/'

Result

After setting up the mirror will be visible in the output docker system infoThe following lines will appear:

 Registry Mirrors:
  https://mirror.gcr.io/

Now you can pull public images as usual: docker pull ubuntu:latest

2. Loading and unloading personal images

In order to securely transfer your own images, you will need a private registry. I use the registry from Yandex Cloud – it is inexpensive, and I already have some automation for them.

Creating a Container Registry

  1. Create an account in Yandex Cloud

  2. Create a directory (folder): Creating a catalog

  3. Open Yandex Cloud Console (see image below)

  4. Select Container Registry (see image below)

  5. I will create a registry with any name

  6. Copy the id of our registry (see the picture below)

  7. Now we substitute the registry id before our images and Docker will understand where to send/from where to pick up images. For example: cr.yandex/crparlvq5pji2gn67f8s/pw_backend:latest

The problem remains that before interacting with the private registry, you need to log in to it. There are 2 ways…

Authorization without authorization

In fact, authorization can be avoided by giving everyone the rights to pull or push images (both are not recommended, the second one especially).

  1. Open the created registry in Yandex Cloud

  2. Open Access bindings (see image below)

  3. Select Assign bindings (see image below)

  4. Select Public + All users (see image below)

  5. Add the necessary rights: puller and/or pusher (see the picture below)

This makes the private registry semi/fully public, but for some it may be the best way to get what they want.

Authorization using keys

Using authorization keys, you can log in to Container Registry once and for all.

  1. Create keys: yc iam key create --service-account-name default-sa -o key.json (cm. documentation)

  2. Let's log in:

    cat key.json | docker login \
      --username json_key \
      --password-stdin \
      cr.yandex

The main problem with this method is that you need to repeat this procedure on each virtual machine, but it can be automated.

  1. We save the key in the project, I will use the path: secrets/yc_key.json (the main thing is not to commit by accident)

  2. Next, we specify in the environment variables

    • SSH_USER — user for SSH

    • SSH_HOST — host for SSH

    • SSH_PKEY — path to the private key for SSH

    export SSH_USER=dbrusenin
    export SSH_HOST=111.222.0.3
    export SSH_PKEY=~/.ssh/id_rsa
  3. Run the script:

    #!/usr/bin/env bash
    
    set -e
    
    function SSH() {
        local cmd="$1"
        ssh -t -o LogLevel=QUIET -o "StrictHostKeyChecking no" -i $SSH_PKEY $SSH_USER@$SSH_HOST "$cmd"
    }
    
    function SCP {
        local src=$1
        local dst_dir=$2
        SSH "mkdir -p \"$(dirname $dst_dir)\""
        scp -i $SSH_PKEY -r $src $SSH_USER@$SSH_HOST:$dst_dir
    }
    
    function DR_YANDEX_AUTH() {
        local auth_keys="$1"
        SCP "$auth_keys" /tmp/yc_key.json
        SSH "cat /tmp/yc_key.json | sudo docker login \
            --username json_key \
            --password-stdin \
            cr.yandex" > /dev/null
        SSH "rm /tmp/yc_key.json"
    }
    
    DR_YANDEX_AUTH "./secrets/yc_key.json"

    Or again a method for the lazy:

    curl https://gist.githubusercontent.com/Deimvis/a60df999aca23b2292f2a5d5c856618a/raw/937d846ab667b84f13a9fb59e012ec3a37afedc8/yandex_auth.sh | sh

Result

Now you can push and pull your own images by prepending the image names cr.yandex/<registry_id>/. You can make sure that you are authorized to the registry by looking at the config ~/.docker/config.json.

Summary

  • Docker Hub is not available in Russia, so it is no longer possible to pull/push images out of the box

  • To pull public images, you need to set up a mirror

  • To push/pull your own images, you need to create a private registry and work through it

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *