一、容器
1.1、docker run 的完整流程

1.2、常用启动选项
一、基础选项:让容器"听话"
这四个选项几乎每次都会用到:
| 选项 | 作用 | 典型场景 |
|---|---|---|
-d |
后台运行(detach) | 跑 Web 服务,如 Nginx |
-it |
交互式终端 | 进容器里敲命令调试 |
--name |
给容器起个名字 | 方便后续管理,不用记一串 ID |
--rm |
退出后自动清理 | 临时跑个一次性命令,不留垃圾 |
bash
# 后台跑一个 Nginx
docker run -d nginx:latest
# 起一个交互式容器玩玩
docker run -it ubuntu:24.04 bash
# 给容器命名
docker run --name myapp nginx:latest
# 跑完就删,干净利落
docker run --rm ubuntu:24.04 echo hi
小技巧:--rm 特别适合调试场景------试完即删,容器列表永远干干净净。
二、端口映射:打通容器和宿主机的通道
容器是隔离的,里面的端口默认外界访问不到,需要显式映射:
bash
# 经典写法:宿主机 8080 → 容器 80
docker run -d -p 8080:80 nginx:latest
# 偷懒写法:-P 让 Docker 随机分配宿主机端口
docker run -d -P nginx:latest
# 安全写法:只监听 localhost,外部访问不到
docker run -d -p 127.0.0.1:8080:80 nginx:latest
格式记法:-p 宿主机端口:容器端口
三、数据卷:让数据活过容器的生命周期
容器删了,里面的数据就没了。挂载数据卷是持久化数据的正确姿势:
bash
# 用命名卷(推荐,Docker 帮你管理)
docker run -v mydata:/data nginx:latest
# 直接挂载宿主机目录(开发时改代码实时生效)
docker run -v /host/path:/container/path nginx:latest
└─ 宿主机 └─ 容器内部
# 只读挂载(容器里的程序改不了你的文件)
docker run -v /host/path:/container/path:ro nginx:latest
四、环境变量:注入配置的两种方式
bash
# 方式一:命令行直接指定(适合一两个变量)
docker run -e MYSQL_ROOT_PASSWORD=secret mysql
# 方式二:从文件加载(变量多了别挤在一行)
docker run --env-file .env myapp
变量一多,.env 文件的优势就显现了------可以进版本控制(配合 .gitignore 排除敏感值)、可以复用、命令行也清爽。
五、资源限制:防止一个容器拖垮整台机器
bash
# 内存上限 512MB,超了会被 OOM Kill
docker run -m 512m nginx:latest
# CPU 上限 1.5 核
docker run --cpus=1.5 nginx:latest
1.3、Docker 容器后台运行:从前台到后台的正确姿势
为什么需要"守护态"运行?
SSH 登录服务器,启动了一个容器,然后不小心关掉了终端窗口------容器也跟着挂了。这显然不是生产环境想要的行为。
在生产环境中,我们希望容器持续运行、不受终端影响,这就是所谓的守护态(daemon mode)运行。核心武器只有一个参数:-d。
先理解一个核心概念:前台 vs 后台
这是 Linux 程序的通用概念,容器也一样:
| 模式 | 表现 | 终端关闭时 |
|---|---|---|
| 前台运行 | 占用终端,输出直接显示 | 程序终止 |
| 后台运行 | 不占用终端,终端可以继续干别的 | 程序继续跑 |
Docker 容器默认是前台运行的。要想让它在后台跑,加上 -d(detach)即可。
前台运行:看看默认行为
不加任何参数直接运行:
bash
$ docker run ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
...
这里用了一个无限循环的脚本,方便观察。你会看到:
- 容器的 STDOUT 直接打印在你的终端上,每秒一行
- 终端被占用,什么都干不了
- 按 Ctrl+C,容器立刻终止
- 直接关掉终端窗口,容器也会停止
前台模式适合交互调试,但用来跑服务就很折磨了。
后台运行:加上 -d 参数
bash
$ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
差别立竿见影:
- 终端立刻释放,可以继续敲其他命令
- 返回的是容器的完整 ID
- 容器在后台默默运行,和终端再无瓜葛
不过有个"副作用":输出不再直接显示了。想看容器在打印什么?用日志命令:
bash
docker logs -f 77b2dc01fe0f # -f 类似 tail -f,实时跟踪
1.4、停止 Docker 容器:stop 和 kill 的区别
终止容器就两个命令:docker stop 和 docker kill。但用错时机,可能让应用丢数据。
一句话结论
docker stop 是"请你有礼貌地退出",docker kill 是"直接拔电源"。
docker stop:优雅停止(推荐)
bash
docker stop 容器名或ID
它的工作流程是:
- 先发送 SIGTERM 信号给容器主进程------"我准备关你了,收拾收拾"
- 应用收到信号后,可以完成手头的工作:保存数据、关闭连接、清理资源
- 等待一段时间(默认 10 秒)后,如果进程还没退出,再发送 SIGKILL 强制杀掉
常用命令
bash
# 等 30 秒再强制终止(给应用充足的收尾时间)
docker stop -t 30 mycontainer
# 等 0 秒 = 立即 SIGKILL(这时和 kill 就没区别了)
docker stop -t 0 mycontainer
# 一次停多个
docker stop container1 container2 container3
# 停止所有运行中的容器
docker stop $(docker ps -q)
docker kill:强制停止
bash
docker kill 容器名或ID
不商量,直接发送 SIGKILL。进程没有任何清理的机会,当场消失。
什么时候才用它?应用已经无响应了------docker stop 等 10 秒也等不来退出,说明进程可能卡死了,这时只能强杀。
1.5、进入 Docker 容器:exec 和 attach
用 -d 把容器跑起来之后,总有需要"进去看看"的时候:查日志、改配置、跑个迁移脚本......Docker 提供了两条路:docker exec 和 docker attach。
先给结论
日常 99% 的场景用 docker exec。docker attach 只在一个场景下有用:看主进程的实时输出。
docker exec:开一个新"窗口"进容器(推荐)
bash
docker exec -it 容器名 /bin/bash
常见参数
| 参数 | 作用 |
|---|---|
-i |
保持标准输入打开(interactive) |
-t |
分配伪终端(TTY) |
-it |
组合使用,才有完整的终端体验 |
-u |
指定用户,如 -u root |
-w |
指定工作目录 |
-e |
临时设置环境变量 |
完整示例
bash
# 后台启动一个容器
docker run -dit --name myubuntu ubuntu
# 进去逛逛
docker exec -it myubuntu bash
root@69d137adef7a:/# ls
root@69d137adef7a:/# exit # 放心退出
# 容器还在跑!
docker ps
# 69d137adef7a ubuntu Up 2 minutes myubuntu
关键就在这儿:exec 是在容器里启动了一个新进程,你退出的是这个 bash 进程,和容器主进程毫无关系。
docker attach:直接"接管"主进程(谨慎)
bash
docker attach 容器名
attach 的工作方式完全不同:它不是开新进程,而是附加到容器主进程(PID 1)的输入输出上。相当于你把终端插到了主进程的插座里。
⚠️ 最大的坑:exit 会带走整个容器
bash
$ docker attach myubuntu
root@243c32535da7:/# exit # 危险!这停止了主进程 → 容器停止
$ docker ps
# STATUS: Exited (0) 2 seconds ago
1.6、删除 Docker 容器:从单个清理到批量大扫除
基本用法
bash
docker rm 容器名或ID
只能删已停止的容器。docker rm 是 docker container rm 的简写,等效。
三个常用选项
| 选项 | 作用 | 适用场景 |
|---|---|---|
| 无参数 | 删除已停止的容器 | 常规操作 |
-f |
强制删除运行中的容器 | 紧急清理 |
-v |
连带删除匿名卷 | 彻底清理,释放磁盘 |
bash
# 运行中的容器直接删会报错
$ docker rm running_container
Error: cannot remove running container
# 加 -f 强删(内部发 SIGKILL,可能丢数据)
$ docker rm -f running_container
# 连同匿名卷一起删
$ docker rm -v mycontainer
批量清理:prune 是主力
清掉所有停止的容器(推荐)
bash
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 150MB
连运行中的也一锅端
bash
# 先全停,再全删
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
# 或者一步到位(慎用!)
docker rm -f $(docker ps -aq)
按条件精准删除
docker ps 的过滤器可以和 rm 配合,实现"选择性清理":
bash
# 删除所有已退出的容器
docker rm $(docker ps -aq -f status=exited)
# 删除名字带 test 的容器
docker rm $(docker ps -aq -f name=test)
# 删除基于 nginx 镜像的所有容器
docker rm $(docker ps -aq -f ancestor=nginx)
# prune 也支持过滤器:清掉 24 小时前的
docker container prune --filter "until=24h"
常用过滤器速查:
| 条件 | 说明 |
|---|---|
status=exited / status=created |
按状态筛选 |
name=xxx |
按名称匹配 |
ancestor=nginx |
基于某镜像创建 |
before=xxx / since=xxx |
按创建时间先后 |
二、仓库
2.1、搭建私有 Docker Registry:一条命令拥有自己的镜像仓库
为什么要搭私有仓库?
Docker Hub 是公开的------公司的镜像不适合往上传,而且拉取速度还得看运气。有些时候你就是想要一个自己说了算的镜像仓库:内网传输快、不用付费、数据在自己手里。
官方早就想到这个需求了,提供了 registry 镜像,一条命令就能跑起来。
第一步:启动 Registry
bash
docker run -d -p 5000:5000 --restart=always --name registry registry:2
四个关键点:
- -p 5000:5000:Registry 默认端口 5000
- --restart=always:崩了自动拉起,生产必备
- 镜像默认存容器内 /var/lib/registry------容器一删数据就没了
所以正经用法要挂数据卷:
bash
docker run -d \
-p 5000:5000 \
-v /opt/data/registry:/var/lib/registry \
--restart=always \
--name registry \
registry:2
镜像实体落在宿主机 /opt/data/registry,备份就是拷目录,简单暴力。
第二步:推送镜像到私有仓库
整个流程三板斧:tag → push → 验证。假设仓库地址是 127.0.0.1:5000。
1. 打标签
Docker 推送到哪个仓库,完全由镜像名字决定,push 命令本身没有"地址"参数。
比如:ubuntu:latest 不是镜像的完整名字,其实它是省略写法,补全了是这样的:
bash
docker.io/library/ubuntu:latest
└──┬──┘ └───┬───┘ └──┬──┘
仓库地址 命名空间 标签
docker.io 就是 Docker Hub 的地址。当你省略不写时,Docker 默认加上 docker.io。
所以 docker push ubuntu:latest 时,Docker 看到名字里的 docker.io,就知道往 Docker Hub 推。
如果镜像名字里写的是你的私有仓库地址:
bash
127.0.0.1:5000/ubuntu:latest
└─────┬─────┘
你的私有仓库地址
docker push 一看到这个前缀,就知道往哪里送了。
bash
格式:docker tag 原镜像[:TAG] [仓库地址/]新名字[:TAG]
$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
打完标签后 docker image ls 会同时出现两个名字------它们指向同一个镜像 ID,只是多了个"远程地址"别名。
2. 推送
bash
$ docker push 127.0.0.1:5000/ubuntu:latest
The push refers to repository [127.0.0.1:5000/ubuntu]
373a30c24545: Pushed
...
latest: digest: sha256:fe427762... size: 1568
3. 验证
Registry 暴露了 HTTP API,用 curl 直接查:
bash
$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":["ubuntu"]}
4. 拉取测试
bash
# 删掉本地副本
docker image rm 127.0.0.1:5000/ubuntu:latest
# 从私有仓库拉回来
docker pull 127.0.0.1:5000/ubuntu:latest
第三步:解决非 HTTPS 的坑(内网必看)
本机 127.0.0.1 推送一切正常,但换成内网地址(比如 192.168.199.100:5000)再推,立刻报错。原因:Docker 默认禁止向非 HTTPS 仓库推送镜像,把它当成不安全的操作拒绝了。
Docker 强制 HTTPS 防的是中间人攻击(MITM):
bash
你的机器 ─────── 网络路径 ─────── 仓库服务器
↑
攻击者在这里截获、篡改数据
- 走环回地址:数据没出过你的机器,攻击者根本没有插手的物理位置,HTTP 明文传输也没人能偷看 → 风险约等于零
- 走内网地址:局域网里其他设备理论上能嗅探、篡改你的镜像数据。推上去的镜像要是被换了,你部署的就是别人给的"特洛伊镜像" → 必须加密
所以 Docker 源码里写死了:localhost/环回地址默认豁免,其余一律要 HTTPS。
解决办法:把内网地址加入白名单。Linux(systemd 系统)编辑 /etc/docker/daemon.json:
bash
{
"insecure-registries": [
"192.168.199.100:5000"
]
}
bash
你的 Docker 客户端 ──HTTP(5000端口)──▶ registry 容器里的进程
│
▼
读写数据目录
/var/lib/registry
(挂到宿主机的话,
就是宿主机上的文件)
私有仓库进阶:给 Registry 加上 HTTPS 和账号密码
之前搭的裸 registry 能用,但有两个硬伤:明文传输、谁都能推送。内网里这么玩迟早出事。现在用 Docker Compose 部署一个带 TLS 加密 + 用户认证 的私有仓库,让 push/pull 都得"持证上岗"。
目标效果:访问走 HTTPS,推送前先 docker login,不登录直接拒。
整体架构预览
bash
docker login / push / pull
│ HTTPS (443)
▼
registry 容器(挂证书 + 密码文件 + 配置)
│
▼
registry-data 数据卷
假设仓库域名是 docker.domain.com,所有操作在一个新文件夹里进行。
第一步:签发 SSL 证书
有域名的话,云服务商的免费证书最省事(还能被浏览器信任)。没域名就自己签------下面用 openssl 搞定"CA 根证书 + 站点证书"的完整流程。
1. 生成 CA 私钥和根证书
bash
# CA 私钥(root-ca.key)= CA 的公章,必须死守,泄露了别人就能冒充你的 CA
openssl genrsa -out "root-ca.key" 4096
# 生成根证书请求(CSR)(-subj 里替换成你自己的信息)
openssl req -new -key "root-ca.key" -out "root-ca.csr" -sha256 \
-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=Your Company Name Docker Registry CA'
第一行:刻公章(生成 CA 私钥)
第二行:填公司注册申请表(CSR)------表里写明了公司名、地址,并盖上公章证明"这是我申请的"
新建 root-ca.cnf:
bash
给根证书写"能力说明书"
[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash
这个 .cnf 文件是经营范围批注------"本公司有权签发证书,最多授权一家分公司,密钥仅限盖章用"。
签发根证书(有效期 10 年):
bash
正式签发根证书
openssl x509 -req -days 3650 -in "root-ca.csr" \
-signkey "root-ca.key" -sha256 -out "root-ca.crt" \
-extfile "root-ca.cnf" -extensions root_ca
root-ca.csr(申请表) ┐
root-ca.key (公章) ├──▶ root-ca.crt(正式根证书)
root-ca.cnf (能力声明) ┘
注意这里签名用的是同一把 key------CA 用自己的私钥给自己的证书签名。这是根证书的标准做法(trust anchor 只能自己证明自己),所谓"自签名证书"就是指这个。
执行完这一步,你的私有 CA 就正式成立、可以营业了。
2. 生成站点证书
给网站(你的 registry)准备"身份证申请材料",流程和前面 CA 的那两步几乎一模一样------区别只在于:这次申请的不是"CA 资格",而是一张具体的站点证书。
bash
# 站点私钥
openssl genrsa -out "docker.domain.com.key" 4096
# 证书请求(CN 必须是你的域名!)
openssl req -new -key "docker.domain.com.key" -out "site.csr" -sha256 \
-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=docker.domain.com'
新建 site.cnf(注意 subjectAltName 把域名和 IP 都写上):
bash
[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:docker.domain.com, IP:127.0.0.1
subjectKeyIdentifier=hash
用根证书签发站点证书:
这一步和之前的 root-ca.cnf 作用相同------给站点证书写"能力说明书"。区别在于:CA 那张说明书声明"我是发证的",这张说明书声明"我是跑服务的"。
bash
openssl x509 -req -days 750 -in "site.csr" -sha256 \
-CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
-out "docker.domain.com.crt" -extfile "site.cnf" -extensions server
3. 整理文件
新建 ssl/ 目录,把这三个文件挪进去,其余删除:
bash
ssl/
├── docker.domain.com.key # 站点私钥
├── docker.domain.com.crt # 站点证书
└── root-ca.crt # CA 根证书
🔐 安全提示:私钥和证书在本地生成,千万别提交进 Git 仓库。
第二步:写 registry 配置文件
registry 默认读 /etc/docker/registry/config.yml,我们在本地写好再挂载进去。
这是registry 服务器的总控台------它告诉 registry:日志怎么记、镜像存哪、谁能访问、用什么证书开 HTTPS、怎么自检健康。
bash
log:
accesslog:
disabled: true # 不记录每次 HTTP 访问日志(推送镜像会产生大量请求,记满磁盘没意义)
level: debug # 日志详细程度:debug 最啰嗦,生产可改 info
formatter: text # 日志格式:纯文本(还有 json 可选)
storage:
delete:
enabled: true # 允许删除镜像(默认关闭!不设 true 的话镜像只能进不能出)
cache:
blobdescriptor: inmemory # 元数据缓存放内存,加快查询
filesystem:
rootdirectory: /var/lib/registry # 镜像文件的存放目录(数据卷挂载点)
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/auth/nginx.htpasswd # 密码文件在容器内的位置
http:
addr: :5000 # 监听端口(容器内的 5000)
host: https://docker.domain.com # 对外宣称的正式地址,生成重定向链接时用
headers:
X-Content-Type-Options: [nosniff] # 安全响应头:防 MIME 类型嗅探攻击
tls:
certificate: /etc/docker/registry/ssl/docker.domain.com.crt # 站点证书
key: /etc/docker/registry/ssl/docker.domain.com.key # 站点私钥
health:
storagedriver:
enabled: true # 开启自检
interval: 10s # 每 10 秒检查一次存储后端是否正常
threshold: 3 # 连续失败 3 次才标记为不健康
重点就三块:auth.htpasswd 开启认证,http.tls 指向证书,其他照抄即可。
第三步:生成账号密码文件
制作"门禁名单"------生成一个存放账号密码的文件,对应上一步配置里 auth.htpasswd.path 指向的那个 nginx.htpasswd。registry 每次收到推送/拉取请求时,都会拿这份名单核对身份。
bash
mkdir auth
docker run --rm \
--entrypoint htpasswd \
httpd:2.4-alpine \
-Bbn username password > auth/nginx.htpasswd
username password 换成你自己的。
第四步:Docker Compose 编排
把 registry 用"声明式配置"的方式部署起来。前面我们准备好了所有物料(证书、密码文件、config.yml),现在要告诉 Docker:"用这些材料,给我跑一个 registry 服务。"
可以不用 Compose,一条 docker run 也能达到同样效果。但 Compose 的价值在于------把整个部署写成一份文件,可重复、可版本管理、可一键启动:
bash
services:
registry: # 服务名,随便起( logs、restart 时用这个名定位它 )
image: registry:2 # 用哪个镜像
ports:
- "443:5000" # 端口映射:宿主机443 ← 容器5000
volumes:
- ./:/etc/docker/registry # 挂载①:配置、证书、密码文件
- registry-data:/var/lib/registry # 挂载②:镜像数据
volumes:
registry-data: # 声明一个命名卷(Docker 自动创建、管理)
第五步:让本机认识这个域名
给本机配一本"本地通讯录"------让本机知道 docker.domain.com 这个域名对应哪个 IP 地址。
没有真实 DNS,先改 hosts 顶上:
bash
# /etc/hosts 添加
127.0.0.1 docker.domain.com
第六步:启动
bash
docker compose up -d
第七步:测试
关键点来了:自己签的 CA 不被系统信任,需要把根证书装进 Docker 的信任目录:
bash
sudo mkdir -p /etc/docker/certs.d/docker.domain.com
sudo cp ssl/root-ca.crt /etc/docker/certs.d/docker.domain.com/ca.crt
然后登录 → 推拉一条龙:
bash
# 登录(输入刚才设置的账号密码)
docker login docker.domain.com
# 推送测试
docker pull ubuntu:24.04
docker tag ubuntu:24.04 docker.domain.com/username/ubuntu:24.04
docker push docker.domain.com/username/ubuntu:24.04
# 删掉本地再拉回来,验证闭环
docker image rm docker.domain.com/username/ubuntu:24.04
docker pull docker.domain.com/username/ubuntu:24.04
最后验证认证真的生效------退出登录再推:
bash
docker logout docker.domain.com
docker push docker.domain.com/username/ubuntu:24.04
no basic auth credentials # ✅ 被拒之门外
看到 no basic auth credentials,说明不登录就推不上去,认证机制工作正常。
总结
一个完整的带 HTTPS 认证的企业级私有镜像仓库搭建方案,核心思路是:自己签发 SSL 证书 → 配置 registry → 部署运行 → 验证推拉。
流程概览:
① 签发 SSL 证书(CA 根证书 + 站点证书)
- 生成 CA 私钥和自签名根证书(自签 = 根证书用自己的私钥给自己签名,这是 trust anchor 的标准做法),有效期 10 年
- 生成站点私钥和 CSR(CN 必须是你的域名)
- 用根证书给站点签发证书(有效期 750 天),通过 .cnf 配置文件声明证书用途(CA声明"我是发证的",站点声明"我是跑服务的")
- 最终只保留 3
个文件:docker.domain.com.key、docker.domain.com.crt、root-ca.crt
② 编写 registry 配置 config.yml
核心三要素:
- auth.htpasswd → 开启账号密码认证
- http.tls → 指向证书和私钥,开启 HTTPS
- storage.delete.enabled: true → 允许删除镜像(默认关闭)
- 另有健康自检、访问日志控制等配置
③ 生成密码文件
用 httpd:2.4-alpine 镜像的 htpasswd 命令生成 Basic Auth 名单,对应配置中的 auth.htpasswd.path
④ Docker Compose 部署
- 镜像 registry:2,端口映射 443:5000
- 挂载配置文件目录 + 命名数据卷(镜像数据持久化)
⑤ 配置 hosts
把域名 docker.domain.com 指向 127.0.0.1(无真实 DNS 时的替代方案)
⑥ 启动服务
docker compose up -d
⑦ 测试闭环
- 关键一步:把自签的 CA 根证书复制到 /etc/docker/certs.d/docker.domain.com/ca.crt,让 Docker 信任它(否则自签证书不被信任)
- 然后 docker login → push → 删除本地镜像 → pull 验证