如何让docker pull使用HTTPS访问仅支持HTTPS的私有镜像仓库
我使用一个未安装dockerd的普通Ubuntu镜像,仅包含Docker命令行客户端和curl,已配置TLS安全,通过第三方容器(dind)运行Docker守护进程。以下curl操作符合预期:
curl https://myregistry/v2/_catalog # 成功获取仓库目录信息 curl http://myregistry/v2/_catalog # 按预期失败(仓库仅支持HTTPS)
但执行docker pull时,Docker客户端却只尝试用HTTP连接,导致失败:
docker pull myregistry/myimage Using default tag: latest Error response from daemon: Client sent an HTTP request to an HTTPS server.
我需要让docker pull默认使用HTTPS而非HTTP,目前找到的资料都是解决“让Docker通过HTTPS访问HTTP仓库”的反向问题,而我的证书配置应该是正确的——毕竟curl的HTTPS请求能正常工作。
更新说明
按照提示,myregistry/image里的myregistry会被识别为命名空间而非仓库主机名,因此我将仓库主机名改为my.hub(包含点的格式会被Docker识别为独立主机)。另外,我没有在任何操作系统、容器或gitlab-runner的config.toml中配置“不安全仓库”。
最小复现示例
我已在/etc/hosts中添加了以下主机映射:myregistry、dind、client1、client2。
以下是完整的复现脚本:
#!/bin/bash # 清理现有容器和数据 docker stop my.hub dind client1 client2 docker rm my.hub dind client1 client2 sudo rm -rf volumes CURRENT=${PWD} HUB_DIR=${PWD}/volumes/my.hub DIND_DIR=${PWD}/volumes/dind # 创建目录结构 sudo rm -rf $CURRENT/volumes mkdir -p $CURRENT/ca mkdir -p $HUB_DIR/certs mkdir -p $DIND_DIR/certs/ca mkdir -p $DIND_DIR/certs/client mkdir -p $DIND_DIR/certs/server mkdir -p $DIND_DIR/etc/docker/certs.d/my.hub mkdir -p $DIND_DIR/etc/docker/certs.d/my.hub:443 mkdir -p $DIND_DIR/usr/local/share/ca-certificates # 生成CA密钥和证书 cd $CURRENT/ca openssl genrsa -out key.pem 2048 openssl req -x509 -days 365 -new -nodes -key key.pem \ -subj "/C=UK/ST=Sussex/L=London/O=Moiself/OU=Sofa/CN=myregistry" \ -sha256 -out cert.pem # 生成my.hub的密钥和证书 cd $HUB_DIR/certs openssl genrsa -out key.pem 2048 openssl req -new -key key.pem -out csr.pem \ -subj "/C=UK/ST=Sussex/L=London/O=Moiself/OU=Sofa/CN=myregistry" \ -addext "subjectAltName=DNS:my.hub,DNS:localhost" openssl x509 -req -days 365 -sha256 -in csr.pem \ -CA $CURRENT/ca/cert.pem -CAkey $CURRENT/ca/key.pem \ -CAcreateserial -out cert.pem \ -extfile <(printf "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment\nsubjectAltName=DNS:my.hub,DNS:localhost") cd $CURRENT # 复制证书到对应目录 cp $CURRENT/ca/cert.pem $DIND_DIR/certs/client/ca.pem cp $CURRENT/ca/cert.pem $DIND_DIR/certs/ca/cert.pem cp $CURRENT/ca/key.pem $DIND_DIR/certs/ca/key.pem cp $CURRENT/ca/cert.pem $DIND_DIR/usr/local/share/ca-certificates/ca.crt cp $HUB_DIR/certs/cert.pem $DIND_DIR/etc/docker/certs.d/my.hub/ca.crt sudo chown -R root volumes # 启动私有镜像仓库(my.hub) docker run -d -it --network some-network --ip 172.18.0.2 -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ -e REGISTRY_HTTP_TLS_KEY=/certs/key.pem -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/cert.pem \ -v $HUB_DIR/certs:/certs:ro --name my.hub --hostname my.hub registry:2 # 启动dind容器(Docker-in-Docker) docker run -d -it --privileged --network some-network --ip 172.18.0.3 --network-alias docker \ -e DOCKER_TLS_CERTDIR=/certs \ -v $DIND_DIR/etc/docker/certs.d:/etc/docker/certs.d:ro \ -v $DIND_DIR/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro \ -v $DIND_DIR/certs/client:/certs/client \ -v $DIND_DIR/certs/ca:/certs/ca \ -v $DIND_DIR/certs/server:/certs/server \ --name dind --hostname dind docker:dind \ sh -c "dockerd-entrypoint.sh --iptables=false && tail -f /dev/null" sleep 3 # 配置dind容器并测试 docker exec -it dind apk update docker exec -it dind apk add curl docker exec -it dind /usr/sbin/update-ca-certificates docker exec -it dind docker pull alpine:latest docker exec -it dind docker tag alpine:latest my.hub/alpine2 docker exec -it dind docker push my.hub/alpine2 docker exec -it dind curl https://my.hub/v2/_catalog docker exec -it dind docker images # 准备client2证书目录 sudo rm -rf $DIND_DIR/certs/client2 sudo cp -r $DIND_DIR/certs/client $DIND_DIR/certs/client2 cp $DIND_DIR/certs/client/cert.pem $DIND_DIR/etc/docker/certs.d/dind/ca.crt # 启动Alpine客户端容器(仅含Docker CLI) docker run -d -it --network some-network --ip 172.18.0.4 \ -e DOCKER_HOST=tcp://dind:2376 \ -e DOCKER_TLS_CERTDIR=/certs \ -v $DIND_DIR/certs/client2:/certs:ro \ -v $DIND_DIR/etc/docker/certs.d:/etc/docker/certs.d:ro \ -v $DIND_DIR/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro \ --name client1 --hostname client1 docker:24.0.0-beta.1-cli-alpine3.17 sleep 3 # 配置client1并测试(以下操作均会出现HTTP请求错误) docker exec -it client1 apk update docker exec -it client1 apk add curl docker exec -it client1 /usr/sbin/update-ca-certificates docker exec -it client1 docker pull alpine:latest; # 发送HTTP请求,报错 docker exec -it client1 docker tag alpine:latest my.hub/alpine3 # 发送HTTP请求,报错 docker exec -it client1 docker push my.hub/alpine3 # 发送HTTP请求,报错 docker exec -it client1 curl https://my.hub/v2/_catalog docker exec -it client1 docker images # 发送HTTP请求,报错 # 启动Ubuntu客户端容器(仅含Docker CLI) docker run -d -it --network some-network --ip 172.18.0.5 \ -e DOCKER_HOST=tcp://dind:2376 \ -e DOCKER_TLS_CERTDIR=/certs \ -v $DIND_DIR/etc/docker/certs.d:/etc/docker/certs.d:ro \ -v $DIND_DIR/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro \ --name client2 --hostname client2 localci-ubuntu-gcc sleep 3 # 配置client2并测试(以下操作均会出现HTTP请求错误) docker exec -it client2 apt update docker exec -it client2 /usr/sbin/update-ca-certificates docker exec -it client2 docker pull alpine:latest # 发送HTTP请求,报错 docker exec -it client2 docker tag alpine:latest my.hub/alpine4 # 发送HTTP请求,报错 docker exec -it client2 docker push my.hub/alpine4 # 发送HTTP请求,报错 docker exec -it client2 curl https://my.hub/v2/_catalog docker exec -it client2 docker images # 发送HTTP请求,报错
解决方案
1. 确认证书与主机名匹配
用以下命令验证证书的SAN字段是否包含my.hub:
openssl x509 -in $HUB_DIR/certs/cert.pem -text -noout | grep -A 3 "Subject Alternative Name"
确保输出中存在DNS:my.hub,否则Docker会拒绝信任证书。
2. 检查dind容器内的dockerd配置
问题核心是dind里的dockerd在转发请求时强制使用HTTP,需做以下检查:
- 进入dind容器,确认
/etc/docker/certs.d/my.hub/ca.crt存在,权限为root:root、644,内容与CA证书一致。 - 检查dockerd启动参数,确认无
--insecure-registry=my.hub配置:docker exec -it dind ps aux | grep dockerd
3. 强制Docker使用HTTPS
两种可选方式:
- 临时方案:拉取时显式指定HTTPS协议和端口:
docker pull https://my.hub:443/myimage - 永久方案:在客户端容器的
~/.docker/config.json中添加配置:
将该文件挂载到客户端容器对应路径(Alpine为{ "registry-configs": { "my.hub": { "Protocol": "https" } } }/root/.docker/config.json,Ubuntu为/home/你的用户名/.docker/config.json)。
4. 排查dockerd日志
查看dind容器内dockerd的日志,定位协议错误根源:
docker logs dind | grep -i "http\|https\|registry"
若日志出现证书信任错误,需在dind容器内重新执行update-ca-certificates并重启dockerd。
内容的提问来源于stack exchange,提问作者Patrick Fromberg

