InstaBox + Docker
常用指令速查表

我的机器 my-machine

记不住没关系,记住套路即可。手机上随手查:点任意命令旁的「复制」就能粘贴使用,顶部搜索框可以按关键词过滤。

🧩

命令的拼法套路:工具 子命令 对象 参数 —— 先想清楚「对什么对象、做什么动作」,命令基本就拼出来了。任何命令后面加 --help,就是它自己的说明书。

没有匹配的命令,换个关键词试试,或点 ✕ 清除搜索。
01

命令是怎么拼出来的

以这条为例,拆开就是四层。看懂一条,其余的都是同一个套路。

ANATOMY
npx instabox@latest exec my-machine -- docker ps
│   │               │    │                   │  └─ 在机器里真正执行的命令
│   │               │    │                   └──── 分隔符:后面是“给机器的命令”
│   │               │    └──────────────────────── 目标机器名
│   │               └───────────────────────────── 动作:在机器里执行命令
│   └───────────────────────────────────────────── instabox 这个工具
└───────────────────────────────────────────────── npx:临时下载并运行这个工具
npx
临时下载并运行这个工具,免安装
instabox@latest
工具本体,@latest 表示用最新版
exec
动作:在机器里执行命令
my-machine
目标机器名,对哪台机器做这个动作
--
分隔符:后面是「给机器的命令」,不再是 instabox 自己的参数
docker ps
在机器里真正执行的命令
💡

规律:先想清楚「对什么对象、做什么动作」,命令基本就拼出来了。

02

InstaBox 常用命令

在本机 PowerShell 里跑。全局安装后不用每次加 npx instabox@latest 前缀:npm install -g instabox。

命令作用
instabox ls看我的机器和状态
instabox ssh <机器名>进入机器的 Shell
instabox exec <机器名> -- <命令>不进 Shell,直接在机器里跑一条命令
instabox create --name N新开一台机器
instabox suspend <机器名> / resume暂停 / 恢复
instabox stop <机器名> / start停机 / 开机
instabox rm <机器名>删除机器(快照不删)
instabox port publish <机器名> <端口>把端口变成公开 HTTPS 地址
instabox port unpublish <机器名> <端口>关闭公开地址
instabox cp [-p] 本地文件 <机器名>:/路径传文件进机器(反过来是拉出来,-p 自动建目录)
instabox snapshot save <机器名> --name N存快照(会先停机,文件保留)
instabox snapshot ls看已有快照
instabox usage看额度与用量
🔑

为什么 instabox ssh 机器名 一句话就能进:普通 SSH 要自己备齐地址、端口、用户名、密钥;instabox 用你本机的登录态向服务器证明身份,服务器确认权限后发临时凭证,CLI 在底下替你把真正的 SSH 连接拨好。方便的代价是信任全押在登录态上 —— 本机别借人乱用,登录态和 API key 按密码级别保管。

💻

换一台电脑登录:账号跟着人走,不跟电脑走。新电脑装好 Node.js 后跑 npx instabox@latest,按提示打开网址、用当初注册的同一个账号输设备码确认;再 npm install -g instabox 装全局版,新开窗口用 instabox ls 验证即可。机器和快照都在账号名下,换电脑不丢;若手滑登成新账号会看到空账号,退出来换回原账号即可。

03

Docker 常用命令

在机器里跑(先 instabox ssh 进去)。主线记住一条链:写配方(Dockerfile)→ 产镜像(build)→ 开容器(run / compose up)。

容器

命令作用
docker ps正在运行的容器
docker ps -a所有容器(含已停止)
docker run -d --name 名字 -p 宿主机端口:容器端口 镜像名后台启动一个容器
docker run hello-world官方问候容器(测试安装用)
docker logs 名字看容器输出日志(-f 持续跟踪,Ctrl+C 退出跟踪,容器不受影响)
docker exec -it 名字 bash进入正在运行的容器内部(-it = 给一个能打字的终端)
docker stop 名字 / docker start 名字停止 / 启动容器
docker restart 名字重启容器
docker rm 名字删除容器(先 stop,-f 强制)
docker stats看各容器 CPU / 内存占用(Ctrl+C 退出)
⚠️

在容器里改的文件只活在这个容器身上,容器一删就没,镜像不变。想把改动存下来 → 写进 Dockerfile 做成镜像;想让数据在容器删除后还活着 → 用数据卷(volumes)。清理顺序永远是先删容器、再删镜像。

镜像

命令作用
docker images本地镜像列表
docker pull 镜像名下载镜像(默认从 Docker Hub 拉)
docker build -t 名字:标签 .按当前目录 Dockerfile 打镜像(最后的 . = 配方在当前目录,别漏)
docker tag 旧名 新名给镜像再起一个名字(推送前改名用)
docker push 镜像名把镜像推到仓库
docker rmi 镜像名删除镜像(被容器引用时先删容器);只想腾磁盘时才删,留着下次 run 不用重下

镜像默认从 Docker Hub(hub.docker.com)拉:nginx 的完整名其实是 docker.io/library/nginx,library 是官方命名空间。其他仓库还有 GitHub 的 ghcr.io、Google 的 gcr.io,企业内部常用自建 Harbor。养成 -t 名字:版本号 的习惯(如 mynginx:v1),以后才分得清哪版是哪版。

Compose 与清理

命令作用
docker compose up -d按 compose.yaml 一次拉起一组服务
docker compose down停掉并清掉这组服务(镜像不删)
docker compose ps / logs看这组服务的状态 / 日志(一组一起看)
docker compose exec 服务名 命令在组内某个服务里执行命令,如 exec cache redis-cli ping(返回 PONG = 活着)
docker system df看 Docker 占了多少磁盘
docker system prune清理无用容器 / 网络 / 镜像(会问确认,慎用)

compose 自动帮你办的事:建专属网络、服务之间用服务名互访(web 连缓存直接用 cache:6379,不用查 IP)、容器统一命名、一条 up 全起、一条 down 全清。看 ps 输出时注意区别:0.0.0.0:8080->80 是对外映射了;只有 6379/tcp 表示只在组内互通、没暴露给宿主机 —— 数据库类服务就该这么藏着。

概念对照

镜像image
= 模板(像安装光盘)
容器container
= 用模板跑起来的实例(像装好并开机的系统)
端口映射-p 8080:80
= 把机器的 8080 端口转发进容器的 80 端口,格式是 外:内
判断口诀rule
定制单个服务的内容 → 改 Dockerfile;协调多个服务的协作 → 改 compose.yaml
🚨

一条红线:日志里 Redis 那句 does not require authentication 是在提醒它没密码。练习时没映射端口、外面连不进来,没事;但永远不要把没密码的 Redis 端口 publish / 映射到公网,这是真实世界里被入侵的重灾区。另外,容器日志时间是 UTC,比本地晚 8 小时,对时间点时别被绕晕。

04

不知道命令时怎么办

按这个顺序来,基本不用死记硬背。

  1. 命令 --help,如 docker run --help、instabox port --help —— 最快
  2. 命令 子命令 --help,逐层缩小
  3. 官方文档:instabox.sh/docs、docs.docker.com
  4. 直接把报错原文贴给 Muse,让它拆给你听 —— 看报错、改拼写(如 cat 网址 应为 curl、dockr 是拼写错误)就是日常排障的常态
📂

什么时候要关心当前目录:只有命令里出现相对路径时才需要 —— 比如 docker build . 的那个点、docker compose up 要找当前目录的 compose.yaml。纯粹的 run / tag / push / pull 跟 daemon 打交道,在哪个目录敲都一样;回家目录用 cd ~(即 /data/home)。

05

InstaBox 这台机器的注意点

  • 家目录在 /data/home,重要文件放 /data 下,快照才会带走
  • 免费档空闲 30 分钟自动暂停;恢复后没设重启策略的 Docker 容器不会自动起来,用 docker start 拉回
  • port publish 的地址是公开的、无鉴权,练习完记得 unpublish
  • instabox exec 机器名 -- 命令 有个怪癖:命令里带 -a 这类参数会被它误解析成自己的参数而报错。两个绕法:命令用引号包成一整串 -- 'docker ps -a',或者直接 instabox ssh 进去敲(练 Docker 时推荐后者)
🌐

publish 的地址能 CNAME 到 Cloudflare 提速吗?不能。那串子域名本身编码了「哪台机器、哪个端口」,网关靠它认人路由,换成你的域名它不认识,大概率直接报 “This URL is not published”;而且 CNAME 只是 DNS 别名,解析到的还是同一批 IP、同一条线路,本身不加速。这类地址是临时调试入口,机器暂停就失效,天生不适合长期绑域名。绑域名长期跑的服务,应该放在自己能控制的机器上(如 Oregon 的 VPS)再走 Cloudflare。

06

Dockerfile 速记

管「一个镜像怎么做出来」,是构建期的事:docker build 从上到下逐行执行,每执行一条就给镜像叠一层。文件名就叫 Dockerfile,没有扩展名。

DOCKERFILE · 我的第一份(~/docker-lesson3)
FROM nginx:latest                                  # 基础镜像(第一行)
COPY index.html /usr/share/nginx/html/index.html   # 拷文件进镜像
指令干什么
FROM指定基础镜像,必须是第一行(构建时)
COPY把文件拷进镜像(构建时)
RUN构建时执行一条命令,如装软件
WORKDIR设定工作目录
ENV设定环境变量(构建时定下、运行时生效)
EXPOSE声明这个镜像监听哪个端口(只是声明,不等于对外发布)
CMD容器启动时默认执行什么(运行时)
⚡

层与缓存:每条指令是一层,层带缓存。只改了 index.html 重新 build,只有 COPY 那层重做,前面的层直接复用,所以第二次特别快。经验法则:不常变的写前面(装依赖),常变的写后面(拷代码)。

compose.yaml 对照

管「一组容器怎么一起跑」,是运行期的事。它不关心镜像内部怎么做,只描述要哪几个服务、用什么镜像、端口怎么映射、谁和谁连。

COMPOSE.YAML · 第四课(~/docker-lesson4)
services:
  web:                    # 服务名,同时也是组内的“通讯录名字”
    image: mynginx:v1     # 用哪个镜像(也可写 build: 现场按 Dockerfile 构建)
    ports:
      - "8080:80"         # 对外映射
  cache:
    image: redis:latest   # 没写 ports,所以只在组内互通
  • 常见字段:volumes(挂数据)、environment(传环境变量)、restart(自动重启策略)、depends_on(声明启动顺序依赖)
  • 起 compose 前先看端口有没有被别的容器占着(如 8080 被 web3 占着时,先 docker stop web3 && docker rm web3)
  • ✓进阶 · Zephyr 镜像:官方镜像先跑通 → 自制瘦镜像(只装 ARM 工具链)→ 编出 hello_world 的 zephyr.bin(待实操)
  • ✓进阶 · Docker Hub:PAT 登录 → mynginx 改名推上去 → 删本地拉回验 digest(待实操)
  • ✓进阶 · GHCR:classic PAT 只勾 packages → 推同一镜像到 ghcr.io;可选:Actions 自动构建推送(待实操)
07

私有镜像仓库(registry)实操流程

镜像默认从 Docker Hub 拉,也可以自己搭一个私有仓库:仓库本身就是个官方镜像。完整闭环是 搭仓库 → 推镜像 → 删本地 → 从仓库拉回来跑。这课对当前目录没要求,在哪敲都行。

BASH · 机器内
# 1. 起仓库(-v 让仓库数据存进 /data,容器删了镜像还在)
docker run -d -p 5000:5000 -v /data/home/registry-data:/var/lib/registry --name registry registry:2
curl http://localhost:5000/v2/            # 返回 {} = 活着
curl http://localhost:5000/v2/_catalog    # 看仓库里有哪些镜像

# 2. 推送:名字第一段带“仓库地址”才知道往哪推
docker tag mynginx:v1 localhost:5000/mynginx:v1
docker push localhost:5000/mynginx:v1
curl http://localhost:5000/v2/mynginx/tags/list   # 看某个镜像的标签

# 3. 拉回:删本地 → 从仓库 pull → digest 指纹一致才算真闭环
docker pull localhost:5000/mynginx:v1
  • 没带仓库前缀的名字(如 mynginx:v1)只是本地名,哪也推不了;docker pull nginx 默认去 Docker Hub。判断门道:名字第一段带不带 “.” 或端口,决定去哪个仓库
  • 想推到 Docker Hub 公共仓库同理:注册账号 docker login 后,docker tag mynginx:v1 你的用户名/mynginx:v1 再 docker push 你的用户名/mynginx:v1
  • 指纹(digest)认内容不认标签:镜像按内容算指纹存(内容寻址),push 的摘要、删除时的 ID、拉回后的 ID 一致(如 4c641273c0b7…),就保证一字节不差;标签能改名,指纹改不了
  • 仓库容器删掉没关系,数据在 /data/home/registry-data 里,下次同样命令再起仓库,镜像都还在 —— 这其实已经摸了一半数据卷
🔒

自建仓库默认无鉴权、走明文 HTTP:只在机器内部练,不要 publish 到公网。哪天想让别的机器来拉,先给它加鉴权和 HTTPS —— 不然等于把仓库敞开在公网上,跟没密码的 Redis 一个道理。

08

数据卷 volumes

让数据活得比容器久。容器自己的文件系统是临时的,存数据只有两种正经办法:

方式写法说明
命名卷-v mydata:/data数据存在 Docker 管的目录里(docker volume inspect 看 Mountpoint),适合数据库等只给容器用的数据
绑定挂载-v /data/home/目录:/data就是宿主机上明明白白的目录,适合网页、配置等要直接看和改的文件
BASH · 机器内
docker volume create mydata
docker run --rm -v mydata:/data alpine sh -c 'echo "saved in volume" > /data/note.txt'
docker run --rm -v mydata:/data alpine cat /data/note.txt   # 换个全新容器也能读到 = 数据在卷里,不在容器里
docker volume ls / inspect 名字 / rm 名字
💡

绑定挂载即时生效:宿主机改文件,容器里立刻变(实测首页 Version 1 → Version 2,没重打镜像、没重启容器)。分工:调试用挂载图方便,交付用 Dockerfile 的 COPY 把文件焊进镜像。compose 里写法:服务下 volumes: - mydata:/data,文件末尾再加顶层 volumes: { mydata: } 声明。仓库那课的 -v /data/home/registry-data 就是绑定挂载。

09

自动重启 --restart

让容器挂了自己爬起来。四种策略:

策略说明
no默认,不重启
on-failure[:N]进程以非 0 退出才拉,最多 N 次
always总是拉
unless-stopped总是拉,除非被手动停过 —— 最常用,推荐
BASH · 自崩演示
# 进程按自己的逻辑退出 = 无人值守的猝死,策略会拉起它
docker run -d --name crash --restart always alpine sh -c 'sleep 3; exit 1'
docker inspect -f '{{.RestartCount}}' crash   # 实测 8 秒涨到 7
docker rm -f crash                            # 失控演示容器一步强拆
  • 只对“进程自己死”生效:程序自崩、自退出 → 策略拉起,RestartCount 增长(上方演示实测)
  • docker stop / docker kill 不触发重启:这是操作员亲手下的停止指令,Docker 视为“手动让他停的”,策略不违抗
  • 容器内部 kill -9 1 杀不掉 PID 1:内核对命名空间 init 进程有信号保护,内部信号不投递;宿主机侧的 docker kill 不受此限
  • InstaBox 场景:机器空闲暂停后恢复 ≈ Docker 重启一轮,容器设 unless-stopped 能自己回来;compose 里写一行 restart: unless-stopped
  • ✓进阶 · Zephyr 镜像:官方镜像先跑通 → 自制瘦镜像(只装 ARM 工具链)→ 编出 hello_world 的 zephyr.bin(待实操)
  • ✓进阶 · Docker Hub:PAT 登录 → mynginx 改名推上去 → 删本地拉回验 digest(待实操)
  • ✓进阶 · GHCR:classic PAT 只勾 packages → 推同一镜像到 ghcr.io;可选:Actions 自动构建推送(待实操)
10

进阶 · Zephyr 编译镜像

把 west 编译环境做成镜像:换机器、换 VPS 都一条命令开工,不用再装一遍工具链。先划清边界——这一章只管编译:InstaBox 没有 USB,ST-Link 插不上,west flash 用不了;容器产出 zephyr.bin 后用 instabox cp 拉回本机再烧。

两条路线:先用现成的,再自己做

路线说明
ghcr.io/zephyrproject-rtos/ci官方镜像(另有带 VNC 的 zephyr-build),含 SDK 全架构工具链,开箱能编 —— 先拿它跑通一次 west build 验证流程
自制 zephyr-dev本章的 Dockerfile:只装 ARM 一条工具链,体积小、版本自己钉死 —— 长期用这条

先定版本:Zephyr 与 SDK 必须配对

Zephyr 版本配套 SDK
4.4 / 4.51.0.1
4.30.17.4
4.20.17.2
3.7 LTS0.16.x;官方镜像用 v0.26-branch 标签
💡

不用背这张表:你工程里 zephyr/SDK_VERSION 文件写着的就是答案。在工作区里跑 west sdk install -t arm-zephyr-eabi 也会自动按它装配对的 SDK——本章把这一步改成构建镜像时用安装包做,道理相同。

Dockerfile:只装 ARM 的瘦镜像(~/zephyr-lesson)

DOCKERFILE · ZEPHYR-DEV
FROM ubuntu:24.04                                  # 基础镜像和 SDK 双钉版,不用 latest
ARG SDK_VERSION=1.0.1                               # 与 Zephyr 4.4/4.5 配对;用 4.3 就改 0.17.4
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y \
      git cmake ninja-build gperf ccache dfu-util device-tree-compiler \
      wget python3-dev python3-venv python3-tk xz-utils file make \
      gcc gcc-multilib g++-multilib libsdl2-dev libmagic1 \
    && rm -rf /var/lib/apt/lists/*              # 主机依赖照官方清单,一层装完清缓存
ENV VIRTUAL_ENV=/opt/venv
ENV PATH=${VIRTUAL_ENV}/bin:${PATH}               # venv 写进 PATH,省掉每次 activate
RUN python3 -m venv ${VIRTUAL_ENV} && pip install --no-cache-dir west
WORKDIR /opt
RUN wget -q https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${SDK_VERSION}/zephyr-sdk-${SDK_VERSION}_linux-x86_64_minimal.tar.xz \
    && tar -xf zephyr-sdk-${SDK_VERSION}_linux-x86_64_minimal.tar.xz \
    && rm zephyr-sdk-${SDK_VERSION}_linux-x86_64_minimal.tar.xz \
    && cd zephyr-sdk-${SDK_VERSION} && ./setup.sh -t arm-zephyr-eabi -h -c   # minimal 包 + 只装 ARM 工具链
ENV ZEPHYR_TOOLCHAIN_VARIANT=zephyr
ENV ZEPHYR_SDK_INSTALL_DIR=/opt/zephyr-sdk-${SDK_VERSION}
WORKDIR /workdir                                    # 源码运行时挂进来,不焊进镜像
CMD ["bash"]
⚠️

体积账先算清:minimal 包下载只要 71MB,只装 ARM 后 SDK 落地约 2.0GB(setup.sh 已实测跑通,产物 gcc 为 14.3.0);全量 gnu 包光下载就 2.1GB、全架构落地更大。InstaBox 的 /data 只有 20GB,还要装 west update 拉的源码(约 4–5GB),所以只装 ARM 不是抠门,是刚需——F103 / F407 / H743 本来也都是 Cortex-M。

构建与使用

BASH · 机器内(~/zephyr-lesson)
docker build -t zephyr-dev:1.0.1 .
docker images zephyr-dev        # 先看一眼体积,心里有账
# 一次性编译(CI 式):编完容器即删,产物留在挂载的工程目录里
docker run --rm -v /data/home/zephyrproject:/workdir zephyr-dev:1.0.1 \
  west build -p always -b nucleo_f103rb zephyr/samples/hello_world
# 交互开发:进容器慢慢敲,退出即删
docker run -it --rm -v /data/home/zephyrproject:/workdir zephyr-dev:1.0.1
ls /data/home/zephyrproject/build/zephyr/zephyr.bin   # 回到宿主机确认产物在
BASH · 容器内第一次进工作区时
west update                     # 拉模块源码(已有工作区且没换版本可跳过)
west zephyr-export              # 注册 Zephyr 的 CMake 包
west packages pip --install     # Python 依赖跟 Zephyr 版本走,装在工作区侧
  • 源码不进镜像,只进工具链:工程目录用绑定挂载进 /workdir,改代码不用重打镜像——这正是第八课的分工:源码用挂载图方便,工具链用 RUN 焊进镜像图稳定
  • 自制板同理:你的板级目录放在应用工程里随工程一起挂载,编译时把 -b nucleo_f103rb 换成自己的板名即可,镜像不用动
  • 产物属主坑:容器默认 root,build/ 会是 root 属主;InstaBox 里本来就用 root 不明显,拿到本机或 VPS 上跑时加 --user $(id -u):$(id -g),免得宿主机删不动产物
  • Python 依赖别焊死在镜像里:它跟 Zephyr 版本绑定,换版本就在工作区重跑 west packages pip --install,镜像只管 west 本体和工具链
  • Dockerfile 若放工程根目录构建,同目录加一份 .dockerignore(写上 build/ 和 .git/),不然构建上下文会把几个 GB 的产物一起打包送进去
🎯

本章完成标准:docker run 里 arm-zephyr-eabi-gcc --version 显示 Zephyr SDK 1.0.1;hello_world 为 nucleo_f103rb 编译通过、zephyr.bin 在宿主机可见;删掉镜像重打一遍,工程和产物都还在;最后把镜像按第七课的方法推一份进自建 registry(localhost:5000/zephyr-dev:1.0.1)收尾。

11

进阶 · 推到 Docker Hub

第七课的闭环换个仓库再走一遍:这次推到 Docker 的公共仓库。流程还是那四步——备令牌 → 登录 → 改名 → 推,最后删本地拉回来验指纹。

推之前先备两样

  • Docker ID:注册 Docker Hub 时的用户名就是你的命名空间,只能小写;镜像名第一段写它,Docker 才知道往你名下推(不写仓库地址时默认就是 Docker Hub)
  • 个人访问令牌(PAT):网页头像 → Account settings → Personal access tokens → Generate new token,权限选 Read & Write。登录时拿它当密码用,不要用账号密码;开了两步验证的账号,CLI 本来也只认令牌
  • 可见性先想好:直接 push 一个新名字会自动新建公开仓库;想私有,先在网页 Repositories → Create Repository 建好并选 Private 再推

流程:推 mynginx 上去再拉回来

BASH · 机器内
docker login                     # Username 填 Docker ID,Password 粘贴 PAT(只显示一次,存好)
docker tag mynginx:v1 <你的DockerID>/mynginx:v1
docker push <你的DockerID>/mynginx:v1     # 输出最后一行 digest: sha256:… 记下它
docker rmi <你的DockerID>/mynginx:v1      # 删本地,准备验闭环
docker pull <你的DockerID>/mynginx:v1     # 拉回时再看 Digest,两次一致才算真闭环
docker run -d -p 8080:80 <你的DockerID>/mynginx:v1   # 能跑起来 = 圆满
  • 报 requested access to the resource is denied:先查两件事——登录了没、名字第一段是不是你自己的 Docker ID(官方排障原话)
  • 网页验收:hub.docker.com → Repositories → mynginx → Tags,能看到 v1 和大小
  • 公开仓库谁都能拉:别人 docker pull <你的DockerID>/mynginx:v1 不用登录就能下,这正是 Hub 的用途——分发,别往里放带私货的镜像
🔑

登录态会进快照:docker login 把凭证写进 ~/.docker/config.json,只是 base64 编码、不是加密。InstaBox 的快照会带走 /data/home,等于令牌跟着快照走——跟 instabox API key 同一个道理。练完 docker logout,不用的令牌去网页吊销。

12

进阶 · GitHub 的仓库 GHCR

GitHub 自家的镜像仓库 ghcr.io:镜像和代码放一个地方,私有权限跟着 GitHub 账号走。命令和上一章几乎一样,差的只是名字前缀和令牌的勾法。

令牌:只勾 packages,别贪多

  • GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token
  • 只勾 write:packages(已含读和推);要能删包再加 delete:packages
  • 坑:网页上勾 write:packages 会连带勾上权限很大的 repo,官方给的绕法是直达链接只勾 packages:github.com/settings/tokens/new?scopes=write:packages

流程:同一个 mynginx 再推一份

BASH · 机器内
export CR_PAT=<你的令牌>            # 令牌先放环境变量,别直接写进命令历史
echo $CR_PAT | docker login ghcr.io -u <你的GitHub用户名> --password-stdin
docker tag mynginx:v1 ghcr.io/<你的GitHub用户名>/mynginx:v1   # 前缀 ghcr.io,必须全小写
docker push ghcr.io/<你的GitHub用户名>/mynginx:v1
docker rmi ghcr.io/<你的GitHub用户名>/mynginx:v1
docker pull ghcr.io/<你的GitHub用户名>/mynginx:v1     # 同样看 Digest 对照
  • 默认私有(和 Hub 相反):第一次推上去的包是私有的,别人拉不到;想公开去 github.com 你的主页 → Packages → 包 → Package settings 改 visibility。私有包拉取前也要先 docker login ghcr.io
  • 命令行推的包默认不关联代码仓库:想关联,在 Dockerfile 加一行 LABEL org.opencontainers.image.source=https://github.com/<用户名>/<仓库名>,关联后包的权限继承仓库
  • 体积红线:单层上限 10GB、单次上传超时 10 分钟——第 10 章的 zephyr-dev(约 2GB)没问题,再大的镜像先掂量
  • 登录态同样进 ~/.docker/config.json:练完 docker logout ghcr.io

进阶一格:让 GitHub Actions 自动推

代码一 push,GitHub 的机器自动 build 并推到 GHCR——用临时的 GITHUB_TOKEN,不用自己保管 PAT。把这份文件放进任意代码仓库的 .github/workflows/docker.yml:

YAML · .GITHUB/WORKFLOWS/DOCKER.YML
name: build-and-push
on: [push]
permissions:
  contents: read
  packages: write          # 没这行 GITHUB_TOKEN 推不动包
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository_owner }}/mynginx:latest

三个仓库怎么选

仓库适合默认可见性
自建 registry(第七课)内网 / 练习 / 不想公开无鉴权,谁能连上谁能推拉
Docker Hub(第 11 章)公开分发、给别人拉新建仓库默认公开
GHCR(本章)跟代码仓库绑定的自用镜像首次推送默认私有
🎯

这两章的验收标准(等你反馈):Hub 和 GHCR 各推成功一次;删本地再拉回,两边输出的 Digest 都和 push 时一致;GHCR 的包在 GitHub 的 Packages 页能看到。哪一步卡住,把报错原文发回来拆。

13

学习进度

截至 2026-10-30:基础课程已实操通过;进阶章「Zephyr 编译镜像」「Docker Hub」「GHCR」三章待实操验证。

已完成 7 / 10
  • ✓第一课:hello-world + nginx 容器、端口映射、port publish
  • ✓第二课:logs / exec 进容器 / stats / 先容器后镜像的清理顺序
  • ✓第三课:Dockerfile 打出自有镜像 mynginx:v1(~/docker-lesson3)
  • ✓第四课:compose 拉起 web + cache(redis)一组服务(~/docker-lesson4)
  • ✓进阶 · 仓库:自建 registry,mynginx 推送 → 删本地 → 拉回,指纹 4c641273c0b7 一致
  • ✓第五课 · 数据卷:命名卷跨容器读回数据;绑定挂载改宿主机文件即时生效
  • ✓第六课 · 自动重启:自崩进程被策略拉起(RestartCount 涨到 7);stop/kill 不触发、容器内杀 PID 1 被内核挡下,均实测确认
  • ✓进阶 · Zephyr 镜像:官方镜像先跑通 → 自制瘦镜像(只装 ARM 工具链)→ 编出 hello_world 的 zephyr.bin(待实操)
  • ✓进阶 · Docker Hub:PAT 登录 → mynginx 改名推上去 → 删本地拉回验 digest(待实操)
  • ✓进阶 · GHCR:classic PAT 只勾 packages → 推同一镜像到 ghcr.io;可选:Actions 自动构建推送(待实操)
已复制,去终端粘贴吧