news 2026/11/1 1:10:39

Docker Registry 离线部署与私有镜像仓库运维指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker Registry 离线部署与私有镜像仓库运维指南

简介:这份 registry.tar.gz 面向无法访问外网的 Docker 使用场景,提供可离线导入的 Registry 镜像文件,帮助运维与开发人员在内网快速搭建私有镜像仓库。包内共 18 个文件,以 json 清单、tar 层数据、version 版本标记及 repositories 仓库索引为主,分别用于描述镜像元信息、承载分层内容与记录版本和仓库映射,压缩包整体约 25.15MB,体积适中,便于通过U盘或内网传输分发。已有 437 人学习下载,说明其在离线部署需求中具有一定参考价值。使用者可借助该镜像包完成私有仓库的基础搭建与本地镜像集中管理,减少对外网拉取的依赖;同时可通过包内清单与层结构了解 Registry 镜像的组织方式,为后续版本核对、离线迁移和内网仓库维护提供可操作的素材。适合具备一定 Docker 基础、需要在内网环境部署镜像服务的技术人员参考使用。

1. registry.tar.gz 到手先判断:二进制包还是镜像包

内网 CI 的最后一步永远是docker push,但出口封着,只能自己搭一个。registry.tar.gz 就是干这件事的:Docker Registry 2.x 的离线部署包,解压、改一份 config.yml、起个进程,5000 端口就成了一个既能 push 又能 pull 的私有镜像仓库。它解决的是没有外网、或者拉到一半被限速拉断的那类问题——镜像分发从「每台机器自己去公网拉」变成「内网一次进、全网分发」。这份包在实务里通常是两种形态:官方 release 的 Go 二进制包,解压出来只有一个registry可执行文件;或者docker save导出的镜像包,里面有 manifest.json 和一层层 layer.tar。形态判断错,后面所有命令都是白费力气。适合自己搭 CI 缓存、做离线交付、或者要给几十台边缘机器一个统一镜像源的人。

2. 解包与启动:把 registry 从压缩包跑成能用服务

2.1 用 tar -tzf 先看清包里装的是什么

拿到包的第一件事不是解压,是看目录。一条命令就能分辨形态,别急着tar -xzf解到当前目录,几十个 hash 目录铺满家目录的场景我见过不止一次。

# 只看前 20 行,不落盘 tar -tzf registry.tar.gz | head -n 20

两种输出对应两条完全不同的落地路线:

输出特征形态落地方式
只有registry、LICENSE、VERSION三个文件二进制包解压到 /opt/registry,systemd 托管
出现manifest.json、repositories、<64位hash>/layer.tar镜像包docker load后以容器运行
出现docker/registry/v2/blobs/...仓库数据目录不是部署包,是备份,走第 6 章迁移

顺手把校验和记下来,内网传文件走 U 盘、跳板机的中转次数多了,包被截断的概率不低:

# 记录原始大小与 sha256,后续任何异常先回来对这两个数 ls -l registry.tar.gz sha256sum registry.tar.gz

head的作用是防止大包刷屏,-z表示按 gzip 解,如果报not in gzip format说明包其实是未压缩的 tar 或者传坏了,换tar -tf再试一次。校验这一步别省,后面docker load报unexpected EOF十有八九就是包不完整。

2.2 二进制方式:五步起一个可写仓库

二进制包的好处是没有容器依赖,坏处是所有路径、用户、权限都得自己管。按顺序走这五步。

# 1. 目录规划:程序、数据、配置分开,升级时只动 /opt install -d /opt/registry /var/lib/registry /etc/registry # 2. 解包并确认可执行 tar -xzf registry.tar.gz -C /opt/registry chmod +x /opt/registry/registry /opt/registry/registry --version # 3. 专用低权限用户,数据目录交给他 useradd --system --no-create-home --shell /usr/sbin/nologin registry chown -R registry:registry /var/lib/registry /opt/registry

第 2 步的--version是必须的,它能顺带验证这个二进制和当前内核架构是否匹配,cannot execute binary file这种错在这一步就能拦住,而不是等到 systemd 里看半天status=203/EXEC。

配置文件先写最小可用版:

# /etc/registry/config.yml version: 0.1 log: level: info formatter: text fields: service: registry storage: filesystem: rootdirectory: /var/lib/registry delete: enabled: true http: addr: :5000 health: storagedriver: enabled: true interval: 10s threshold: 3

version: 0.1是配置格式版本,不是软件版本,别改。delete.enabled默认不开,意味着删除镜像的 API 会直接返回 UNSUPPORTED,很多人第一次删镜像失败就是漏了这一行。health.storagedriver打开后,/debug/health会真的去写一次存储,比单纯看端口通不通靠谱。

然后交给 systemd,别用nohup ./registry serve &这种跑法——SSH 一断进程就没了,日志也跟着没:

# /etc/systemd/system/registry.service [Unit] Description=Docker Registry After=network-online.target Wants=network-online.target [Service] Type=simple User=registry Group=registry ExecStart=/opt/registry/registry serve /etc/registry/config.yml Restart=always RestartSec=3 LimitNOFILE=65536 [Install] WantedBy=multi-user.target
systemctl daemon-reload systemctl enable --now registry systemctl status registry --no-pager curl -s http://127.0.0.1:5000/v2/ # 期望返回 {}

LimitNOFILE=65536不是凑数,大镜像并发推的时候默认 1024 的文件描述符很容易打满,表现是 push 到 80% 突然connection reset。Restart=always保证磁盘抖动导致的崩溃能自己起来,配合RestartSec=3避免疯狂重启刷日志。

2.3 镜像方式:docker load 之后别急着 docker run

如果确认是镜像包,先导入再看 tag,不要凭印象写registry:2:

# 导入并确认实际 tag docker load -i registry.tar.gz docker images | grep -i registry
# 起容器,两个挂载点必须对:数据目录和配置文件 docker run -d --name registry --restart=always \ -p 5000:5000 \ -v /srv/registry/data:/var/lib/registry \ -v /srv/registry/config.yml:/etc/docker/registry/config.yml:ro \ registry:2

两个挂载路径是镜像内约定死的:镜像里的默认配置指向/var/lib/registry和/etc/docker/registry/config.yml。只挂数据不挂配置,你改的 config.yml 根本不会生效,人在宿主机上改了半小时,容器里跑的还是默认值——这种黑匣子式的困惑,用docker exec registry cat /etc/docker/registry/config.yml一比就原形毕露。

配置文件冒号后加:ro,防止容器进程意外回写。数据目录/srv/registry/data要提前mkdir -p,Docker 自动创建的空目录属主是 root,容器内如果换了非 root 用户会写不进去。

2.4 第一次 push 闭环:三条命令验证到底通没通

服务起来不等于能用,最小闭环是 push 一个真实镜像再拉回来。

# 打本地 tag,注意仓库名格式是 host:port/name:tag docker tag nginx:1.25 127.0.0.1:5000/demo/nginx:1.25 # 推 docker push 127.0.0.1:5000/demo/nginx:1.25 # 从 API 侧确认,别只信 docker 的返回 curl -s http://127.0.0.1:5000/v2/_catalog curl -s http://127.0.0.1:5000/v2/demo/nginx/tags/list

期望看到{"repositories":["demo/nginx"]}和{"name":"demo/nginx","tags":["1.25"]}。_catalog在仓库条目多的时候会分页,加?n=100并读响应头里的Link才能翻全。

镜像名里的127.0.0.1:5000不能省成demo/nginx,少了 host 部分 Docker 会默认往 Docker Hub 推。另外第一次 push 如果卡在Waiting很久,先在宿主机curl一下/v2/,服务端没起来和网络不通是两回事,别混在一起查。

3. config.yml 逐项拆解:存储、监听、日志的参数怎么定

3.1 log 段:先让日志能对上号

多实例或者多环境的时候,日志里没有标识是最难受的——出问题翻日志,一堆一模一样的行,分不清是测试环境还是生产。

log: accesslog: disabled: false # 打开访问日志,push/pull 的每条请求都有记录 level: info # debug 会打出每次 blob 校验,只在排障时开 formatter: text # 要接日志系统就换 json fields: service: registry environment: prod region: internal

accesslog.disabled: false是最值得开的一项,默认它是关的,导致客户端报 4xx/5xx 时服务端一片安静。开完之后每行包含 method、path、status、duration,定位「谁在什么时候推了什么」非常直接。

level调到debug会输出 blob 层的 digest 校验信息,能查出「明明推上去了为什么 pull 下来 digest 不一致」这类问题,但量极大,日常别开。formatter: json配合 filebeat 之类的采集器更省事,纯文本人眼看更舒服,按团队习惯选。

fields下挂的键值会附加到每一行日志,值建议跟着部署环境走,不要写死成某个具体业务名。

3.2 storage 段:目录布局与删除开关

存储是 registry 最容易出事的地方,参数不多但每个都影响数据安全。

storage: filesystem: rootdirectory: /var/lib/registry maxthreads: 100 # 并发读写文件的上限,机械盘要调低 delete: enabled: true # 不开则 DELETE API 全部返回 UNSUPPORTED cache: blobdescriptor: inmemory # 缓存 blob 描述符,减少重复 stat maintenance: uploadpurging: enabled: true age: 168h # 超过 7 天的未完成上传视为垃圾 interval: 24h dryrun: false

rootdirectory落盘后的结构是固定的:docker/registry/v2/blobs/sha256/ab/abcdef.../data存实际层数据,docker/registry/v2/repositories/<name>/_manifests/存清单引用。这个布局只能由 registry 自己维护,手工 mv 或 rm 会把引用关系搞断,之后 pull 报manifest unknown而 blobs 明明还在。

maintenance.uploadpurging默认就开着,但dryrun一定要确认是false。有人为了「安全」把它设成 true 又忘了改回来,结果是每次 CI 中断留下的半截上传永远堆在磁盘里,一年后磁盘告警才发现里面全是没用的 chunk。

cache.blobdescriptor: inmemory对单机够用,重启后缓存重建,只影响启动后第一波请求的延迟。仓库规模上了几十万 blob 之后,可以考虑换成redis,但那是另一个量级的运维成本,别一上来就上。

3.3 http 段:addr、host 与反代下的坑

http: addr: :5000 host: https://registry.local:5000 # 影响响应里的 Location 头 headers: X-Content-Type-Options: [nosniff] http2: disabled: false

addr: :5000表示监听所有网卡的 5000,只给本机反代用的话写127.0.0.1:5000更安全。host这个字段很多人不填,后果是 registry 在做分块上传时返回的Location用的是客户端请求里的 Host,一旦中间隔了一层反代并改写 Host,客户端就会去访问内网地址而失败,报错通常是no such host或者莫名的重定向。

headers下的X-Content-Type-Options: [nosniff]是官方默认值,保留即可,别删。

http2.disabled保持 false,但要注意如果前面挂的 nginx 已经做了 TLS 终止,registry 自身就该跑 HTTP,TLS 相关配置全部交给反代,两边都配 TLS 会出现「反代连不上后端」这种低级的 502。

3.4 用环境变量覆盖配置:不动文件改参数

容器化部署时改文件要重建镜像或者多挂一份配置,环境变量覆盖更顺手。

# 规则:REGISTRY_ 前缀 + 配置路径的层级,点换成下划线,全大写 REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/srv/registry/data REGISTRY_HTTP_ADDR=0.0.0.0:5000 REGISTRY_LOG_LEVEL=debug REGISTRY_STORAGE_DELETE_ENABLED=true
# 验证覆盖是否生效:容器内看最终配置 docker exec registry cat /etc/docker/registry/config.yml docker logs registry 2>&1 | head -n 20

映射逻辑就是storage.filesystem.rootdirectory→REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY,中间每一层用单个下划线连接。有一个边界要注意:如果某个配置键本身名字里带下划线,用环境变量表达会产生歧义,这种情况别硬套,老老实实改配置文件。

环境变量只在启动时读一次,改完必须docker restart或systemctl restart。还有一种情况是同时写了配置文件和环境变量,环境变量优先级更高,排查时先确认有没有残留的REGISTRY_*在.env或者 compose 文件里——docker inspect registry | grep -A20 Env一眼就能看出来。

4. 认证与 TLS:把裸奔的 5000 端口收口

内网不等于可信网络,一个没有任何认证的 registry 意味着任何能连上 5000 端口的人都能删你的镜像。

4.1 htpasswd:一行命令生成凭证文件

registry 只认 htpasswd 格式,认 bcrypt,不认明文。

# 有系统自带工具就用这套(httpd-tools / apache2-utils) htpasswd -Bbn deploy 'ChangeMe_2024' > /etc/registry/htpasswd # 没装的话用官方 httpd 镜像里的 htpasswd,效果一样 docker run --rm --entrypoint htpasswd httpd:2 \ -Bbn deploy 'ChangeMe_2024' > /etc/registry/htpasswd chmod 640 /etc/registry/htpasswd chown root:registry /etc/registry/htpasswd

三个参数各有含义:-B指定 bcrypt(不加就是 MD5 crypt,registry 直接拒绝),-b表示密码从命令行给(不加则交互输入),-n表示结果打印到标准输出而不是写文件。生产环境建议去掉-b走交互输入,避免密码进 shell 历史。

密码用单引号包住,含$、!、&的密码在双引号里会被 shell 展开,生成出来的 hash 对不上,登录一直 401,查半天以为是配置问题。

挂进配置:

auth: htpasswd: realm: basic-realm # 401 响应里 WWW-Authenticate 的值 path: /etc/registry/htpasswd

bcrypt 对超过 72 字节的密码会静默截断,超长密码设了等于没设后半段,密码策略里限制一下长度更稳。

4.2 自签证书:CN 和 SAN 两个都得对

内网没有公网证书,自签是常规操作,但签发参数错了客户端一定翻车。

install -d /etc/registry/certs openssl req -x509 -nodes -newkey rsa:4096 -days 3650 -sha256 \ -keyout /etc/registry/certs/registry.key \ -out /etc/registry/certs/registry.crt \ -subj "/C=CN/O=internal/CN=registry.local" \ -addext "subjectAltName=DNS:registry.local,IP=192.168.10.20" chmod 600 /etc/registry/certs/registry.key openssl x509 -in /etc/registry/certs/registry.crt -noout -text | grep -A2 "Subject Alternative Name"

关键在-addext "subjectAltName=..."。现代 Docker 客户端只看 SAN,不看 CN,只写 CN 的证书在客户端眼里就是「证书与主机名不匹配」。用 IP 访问就得把 IP 写进 SAN,用域名访问就写 DNS,两个都要就都列上,逗号分隔。

-days 3650是十年,内网自签图省事可以,但一定要在日历上记一笔,证书过期那天的报错是certificate has expired or is not yet valid,跟配置错误长得完全不一样。

-addext需要 OpenSSL 1.1.1 以上,老系统上没有这个参数,得写一份带[v3_req]段的 openssl.cnf 用-config指定,思路一样,只是啰嗦。

服务端挂上:

http: addr: :5000 host: https://registry.local:5000 tls: certificate: /etc/registry/certs/registry.crt key: /etc/registry/certs/registry.key

4.3 客户端信任:certs.d 目录名必须带端口

服务端配好 TLS 只是第一步,每台要 push 的机器都得信任这张自签证书。

# 目录名必须是 host:port 的完整形式,缺端口不生效 install -d /etc/docker/certs.d/registry.local:5000 cp /etc/registry/certs/registry.crt /etc/docker/certs.d/registry.local:5000/ca.crt systemctl restart docker

文件名固定叫ca.crt,不能改。目录名里端口是硬性要求——/etc/docker/certs.d/registry.local/这种写法对registry.local:5000完全不生效,这是最典型的静默失败:目录建了、证书拷了、docker 也重启了,push 还是x509: certificate signed by unknown authority。

用 IP 访问的场景,目录名就写192.168.10.20:5000,并且这个 IP 必须在证书 SAN 里。两者对不上,证书信任链会卡在主机名校验这一关。

证书要分发给所有节点,不要指望每人手工cp,CI 镜像构建节点统一走配置管理或者初始化脚本下发,改动一处、全部生效。

4.4 反向代理收口:nginx 三处必须改

想让 registry 走 443、走统一域名、顺便加访问控制,就得挂反代。下面这份配置里三个参数是关键:

server { listen 443 ssl; server_name registry.local; ssl_certificate /etc/nginx/certs/registry.crt; ssl_certificate_key /etc/nginx/certs/registry.key; client_max_body_size 0; # 0 = 不限制,大镜像必需 chunked_transfer_encoding on; # registry 分块上传依赖这个 location /v2/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $http_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; # 大层上传别 60 秒就断 proxy_request_buffering off; # 边收边转发,省磁盘 } }

client_max_body_size 0不设,默认 1MB,推一个基础镜像立刻 413。chunked_transfer_encoding on不开,分块上传协议走不通,表现为 push 卡在某一层反复重试。proxy_set_header Host $http_host必须用$http_host而不是$host,后者会丢掉端口,registry 返回的 Location 头就少了:5000,客户端跟着去连 443 之外的端口失败。

proxy_request_buffering off加上之后,上传的大层不会先在 nginx 落一份临时文件再转发,省一半磁盘 IO。代价是后端必须撑得住慢速客户端,registry 本身没问题。

5. 避坑与排查:五个把私有仓库搞挂的现场

5.1 推送侧的三个经典报错

现象:docker push报http: server gave HTTP response to HTTPS client,服务端日志里一条请求都没有。

原因:Docker 客户端默认只走 HTTPS 访问非 localhost 的仓库,服务端跑的是明文 HTTP,握手阶段就断了,请求根本没到 registry。

解决:两条路,要么按 4.2、4.3 上 TLS;要么明确告诉客户端这是不安全仓库,在/etc/docker/daemon.json里加:

{ "insecure-registries": ["192.168.10.20:5000"] }

改完systemctl reload docker,再用docker info | grep -A5 "Insecure Registries"确认生效。注意这个字段是覆盖写,已有配置要保留,别整个文件重写。

现象:改完 daemon.json,systemctl restart docker直接失败,所有容器停摆。

原因:JSON 里写了注释、末尾多了逗号、或者insecure-registries写成了下划线。Docker 启动时解析失败会拒绝启动,而很多人是在生产机上直接改的。

解决:改之前先备份,改完用python3 -m json.tool /etc/docker/daemon.json或jq . /etc/docker/daemon.json校验语法,通过了再重启。远程操作时留一条回滚命令在手边,别把自己关在门外。

现象:大镜像推送到 90% 左右断掉,重试还是断在同一位置。

原因:反代的client_max_body_size或proxy_read_timeout卡住了;也可能是服务端LimitNOFILE太小,高并发时 fd 耗尽。

解决:按 4.4 把 nginx 三个参数改掉,服务端 systemd unit 里加LimitNOFILE=65536,然后看journalctl -u registry有没有too many open files。磁盘满也会表现成类似症状,df -h一起看。

5.2 存储侧的坑:删了镜像磁盘不降

现象:从仓库删了 tag、删了 manifest,du -sh /var/lib/registry一点没变。

原因:registry 的删除是软删除。删 manifest 只是把引用摘掉,blobs/sha256/下的实际数据原封不动留在磁盘上,必须跑垃圾回收才真正释放。

解决:按第 6 章顺序走:删 tag → 删 manifest → 停写 →registry garbage-collect。另外注意共享层,多个镜像共用同一个 base layer 时,删掉其中一个镜像那一层也不会被回收,因为还有别的 manifest 引用它。指望删一个镜像腾出 2GB 是不现实的。

现象:磁盘写满之后 push 全部失败,清出空间了还是失败。

原因:上传中断留下的临时 chunk 还在,registry 启动时扫描存储会报错退出,或者清理逻辑把整个上传目录拖垮。

解决:先df -h确认,清空间,然后检查uploadpurging是否dryrun: false。恢复后先curl /v2/确认服务活着,再跑一次 GC 清掉半截上传。

5.3 排查顺序:三条命令定位到底卡在哪一层

遇到 push 失败,别从代码改起,按从近到远三层确认:

# 1. 服务端本身活没活 curl -sv http://127.0.0.1:5000/v2/ 2>&1 | tail -n 20 # 2. 服务端日志有没有收到请求 journalctl -u registry -n 100 --no-pager | tail -n 30 # 3. 客户端认可不认可这个仓库 docker info 2>/dev/null | grep -A5 "Insecure Registries" docker info 2>/dev/null | grep -A5 "Registry Mirrors"

第 1 条返回{}说明服务正常,问题在客户端或网络;返回connection refused说明进程没起来,直接看第 2 条;返回 TLS 相关错误说明证书链有问题,回去查 4.2、4.3。第 3 条用来排除「配置没生效」——改完配置没重启 Docker、或者改的是另一个 daemon.json(比如 systemd unit 里用--config-file指了别的路径),这两种情况docker info一眼能看出来。

日志里 401 说明认证没通过,503 说明存储层有问题(磁盘满、权限不对、目录不存在),400 多半是路径或参数不规范。把状态码和后端日志对上,比翻客户端那一句denied: requested access to the resource is denied有用得多。

6. 垃圾回收与迁移验证:把数据目录当成一等公民

删除和回收必须拆开做,顺序错了就是数据损坏。

# 1. 拿 digest,注意 Accept 头,否则拿到的是 schema1 的清单 curl -sI -u deploy:pass \ -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ https://registry.local/v2/demo/nginx/manifests/1.25 | grep -i docker-content-digest # 2. 删 manifest curl -X DELETE -u deploy:pass \ https://registry.local/v2/demo/nginx/manifests/sha256:xxxx # 3. 停写窗口内做 GC,--dry-run 先跑一遍看清单 systemctl stop registry /opt/registry/registry garbage-collect --dry-run /etc/registry/config.yml | head -n 40 /opt/registry/registry garbage-collect --delete-untagged /etc/registry/config.yml systemctl start registry

GC 期间绝对不能有写入,边写边扫会让扫描器把正在被引用的 blob 判成孤儿删掉,后果是部分镜像永远拉不全,而且报错信息完全指不到根因。--delete-untagged顺手清掉没有 tag 的清单,--dry-run先看一遍输出确认规模,别在高峰期直接扫。

迁移换机器时,直接rsync -a整个rootdirectory,保持属主和权限:

rsync -aHAX --delete /var/lib/registry/ newhost:/var/lib/registry/

-H保硬链接、-A保 ACL,缺了这两个标志,blob 的硬链接会被展开成独立文件,体积翻好几倍。迁完先跑 GC 的--dry-run,能正常扫完不报错,基本就说明数据完整。

我踩过一次最疼的坑:为了「省事」在业务高峰期跑 GC,第二天有三分之一的镜像拉不下来,回滚备份花了大半天。从那以后我每次动存储之前,都强制走一遍「备份目录 → dry-run → 停写窗口 → 正式 GC → 抽查 pull」这五步,一步不跳,宁可多停五分钟。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 1:10:38

DRV8818PWPR与采样电阻R7KA8D2KFLCAC的高精度步进驱动设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/1 1:09:49

ATmega32+DRV8818PWPR工业级双极步进电机精准驱动方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/1 1:09:35

SSM产品管理系统源码实战:从环境搭建到核心代码改造与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/1 1:08:22

基于DRV8818与PIC32MZ的工业步进电机驱动方案解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/1 1:07:39

Docker私有仓库离线部署:registry.tar.gz起服务、认证与GC

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华