我之前发版一直是手工:

1
2
3
4
5
本地 build 镜像
docker save 成 tar
FTP 上传到服务器
服务器 docker load
重启容器

能用,但每次都很烦。镜像稍微大一点,上传和 load 都慢,也没有一个清楚的版本记录。所以根据以上步骤整理出来的需求如下:

  • CI 能把业务镜像推上去
  • 生产机器能拉下来
  • 最好还能缓存一下 Docker Hub 的基础镜像

这种场景用 registry:3 就够了。

前提

由于 registry:3 限制,一个 registry cache 只能代理一个上游,所以私有镜像仓库和 Docker Hub 缓存分开运行。

比如:

1
2
registry.example.com  # 存自己的业务镜像
mirror.example.com    # 缓存(加速) Docker Hub 镜像

私有 Registry

先建目录:

1
2
mkdir -p /opt/registry/{data,auth}
cd /opt/registry

生成登录账号:

1
2
docker run --rm --entrypoint htpasswd httpd:2 \
  -Bbn woodpecker 'your-password' > auth/htpasswd

docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
services:
  registry:
    image: registry:3
    container_name: private-registry
    restart: always
    ports:
      - "127.0.0.1:5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      OTEL_TRACES_EXPORTER: none
    volumes:
      - ./data:/var/lib/registry
      - ./auth:/auth

启动:

1
docker compose up -d

这里只监听 127.0.0.1,外面统一走 Nginx / Caddy 反代 HTTPS。

Nginx 大致配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
server {
    listen 443 ssl http2;
    server_name registry.example.com;

    ssl_certificate /etc/letsencrypt/live/registry.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/registry.example.com/privkey.pem;

    client_max_body_size 0;

    location / {
        proxy_pass http://127.0.0.1:5000;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 900;
    }
}

client_max_body_size 0 不要漏。镜像层可能比较大,限制太小 push 会失败。

测试:

1
2
3
docker login registry.example.com
docker build -t registry.example.com/my-app/backend:latest .
docker push registry.example.com/my-app/backend:latest

生产服务器只需要登录一次:

1
docker login registry.example.com

之后更新服务:

1
2
docker compose pull
docker compose up -d

到这里,原来的 save tar + FTP + load 就可以扔掉了。

Docker Hub 缓存(加速)

单独建一个 mirror:

1
2
mkdir -p /opt/registry-mirror/data
cd /opt/registry-mirror

config.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
version: 0.1

storage:
  filesystem:
    rootdirectory: /var/lib/registry
  delete:
    enabled: true

http:
  addr: :5000

proxy:
  remoteurl: https://registry-1.docker.io
  ttl: 168h

docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
services:
  registry-mirror:
    image: registry:3
    container_name: registry-mirror
    restart: always
    ports:
      - "127.0.0.1:5001:5000"
    volumes:
      - ./data:/var/lib/registry
      - ./config.yml:/etc/docker/registry/config.yml
    environment:
      OTEL_TRACES_EXPORTER: none

启动:

1
docker compose up -d

然后给 mirror.example.com 配 HTTPS 反代到 127.0.0.1:5001

使用方式

方法1: 客户端配置 Docker daemon:

1
2
3
{
  "registry-mirrors": ["https://mirror.example.com"]
}

重启 Docker:

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

测试:

1
docker pull alpine:latest

第一次还是会去 Docker Hub 拉,后面相同镜像就会走本地缓存。

方法2: 临时拉取镜像

镜像名称前加上自己的域名。

1
docker pull mirror.example.com/alpine:latest

注意

  1. 一个 registry cache 只能代理一个上游。如果想代理 ghcr.ioquay.io 这些地址,则需要建立多个容器。

  2. 私有 Registry 最好走 HTTPS。HTTP 也能配,但每台机器都要单独加 insecure registry。

  3. registry:3 + htpasswd 的权限很简单,基本就是能登录就能 push/pull。如果要项目级权限、Web UI、漏洞扫描、保留策略,那就需要 Harbor 之类的。

另外,Registry 的数据不会因为 tag 改了就自动变小。镜像多了以后要考虑清理策略。