概述
容器命令背下来不难,难的是第一次真动手时每一步都卡壳:端口到底谁映射谁、进了容器发现没装 vi、改完文件浏览器还是老页面、再想重跑一次又提示容器名已存在。下面把两道练习题按"命令 + 预期输出 + 验证点"的节奏走一遍,照着敲就能复现。
纲要
- 关键概念
- 容器四种状态:运行、暂停、停止、删除,以及状态之间怎么迁移
- 端口映射的方向:
-p 宿主机端口:容器端口 docker exec与docker attach的区别,-it参数到底给了什么- Redis 的 RDB 与 AOF 两种持久化,
--appendonly yes是何意
- 核心流程
- 练习一:创建带 AOF 持久化的 Redis 容器 →
exec进入 → 写入数据 → 停止重启后数据仍在 - 练习二:创建 Nginx 容器 → 进入改
index.html→ 浏览器 / curl 验证 → 停止 → 重启 → 再验证 → 删除 - 容器内没有
vi时,用sed -i或echo >改文件
- 练习一:创建带 AOF 持久化的 Redis 容器 →
- 涉及命令与配置
docker run --name <名> -p <宿主>:<容器> -d <镜像>docker exec -it <名> bash/docker exec -it <名> redis-clidocker ps/docker ps -a/docker logs/docker stop/docker start/docker rm- Nginx 静态目录
/usr/share/nginx/html(讲义 day03-Docker/2.2.3 场景)
两道练习题要你做什么
两道题对应两个真实场景,命令集合基本一致,差别在于"改文件"和"写数据":
| 题目 | 要求 | 主要考察的命令 |
|---|---|---|
| 练习一(视频演示) | 创建并运行一个 Redis 容器,支持数据持久化,然后进入容器写入一个 key | run -p --appendonly exec redis-cli |
| 练习二(讲义 2.2.3) | 创建 Nginx 容器映射 80 端口,进入容器改 index.html,再从外部验证改动 |
run -p exec sed stop start rm |
先把练习一做掉,它验证的是"数据活不活得过重启";练习二验证的是"容器内文件能不能改、改完外面看不看得到"。
容器状态与命令速查
容器的状态迁移是整套练习的骨架,先记住这张图再动手:
#mermaid-svg-a6LORel1dZjewXpR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a6LORel1dZjewXpR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a6LORel1dZjewXpR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a6LORel1dZjewXpR .error-icon{fill:#552222;}#mermaid-svg-a6LORel1dZjewXpR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a6LORel1dZjewXpR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a6LORel1dZjewXpR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a6LORel1dZjewXpR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a6LORel1dZjewXpR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a6LORel1dZjewXpR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a6LORel1dZjewXpR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a6LORel1dZjewXpR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a6LORel1dZjewXpR .marker.cross{stroke:#333333;}#mermaid-svg-a6LORel1dZjewXpR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a6LORel1dZjewXpR p{margin:0;}#mermaid-svg-a6LORel1dZjewXpR defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-a6LORel1dZjewXpR g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-a6LORel1dZjewXpR g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-a6LORel1dZjewXpR g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-a6LORel1dZjewXpR g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-a6LORel1dZjewXpR g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-a6LORel1dZjewXpR .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-a6LORel1dZjewXpR .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-a6LORel1dZjewXpR .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-a6LORel1dZjewXpR .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-a6LORel1dZjewXpR .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-a6LORel1dZjewXpR .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-a6LORel1dZjewXpR .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-a6LORel1dZjewXpR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a6LORel1dZjewXpR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a6LORel1dZjewXpR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a6LORel1dZjewXpR .edgeLabel .label text{fill:#333;}#mermaid-svg-a6LORel1dZjewXpR .label div .edgeLabel{color:#333;}#mermaid-svg-a6LORel1dZjewXpR .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-a6LORel1dZjewXpR .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-a6LORel1dZjewXpR .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-a6LORel1dZjewXpR .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-a6LORel1dZjewXpR .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-a6LORel1dZjewXpR .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a6LORel1dZjewXpR .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a6LORel1dZjewXpR #statediagram-barbEnd{fill:#333333;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a6LORel1dZjewXpR .cluster-label,#mermaid-svg-a6LORel1dZjewXpR .nodeLabel{color:#131300;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-a6LORel1dZjewXpR .note-edge{stroke-dasharray:5;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-note text{fill:black;}#mermaid-svg-a6LORel1dZjewXpR .statediagram-note .nodeLabel{color:black;}#mermaid-svg-a6LORel1dZjewXpR .statediagram .edgeLabel{color:red;}#mermaid-svg-a6LORel1dZjewXpR #dependencyStart,#mermaid-svg-a6LORel1dZjewXpR #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-a6LORel1dZjewXpR .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a6LORel1dZjewXpR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker run / docker start
docker pause
docker unpause
docker stop
docker start
docker rm
docker rm -f
运行中
暂停
停止
对应的命令职责:
| 命令 | 作用 | 一句话提醒 |
|---|---|---|
docker run |
创建并启动容器 | 每次都会新建一个容器 |
docker ps |
看运行中的容器 | 加 -a 才能看到已停止的 |
docker stop |
停止容器 | 先发 SIGTERM,10 秒后补 SIGKILL |
docker start |
启动已停止的容器 | 不会重建,配置沿用创建时的 |
docker exec |
在运行中的容器里执行命令 | 容器必须是运行态 |
docker rm |
删除容器 | 运行中的删不掉,除非加 -f |
stop 之后容器还在,只是进程没了、资源回收了;rm 才是真正把它从磁盘上抹掉。这个区别在练习二结尾会体现出来。
练习一:Redis 容器,创建并支持持久化
先看官方给的运行命令
Redis 官方镜像在 Docker Hub 上直接给了带持久化的启动方式,比手搓参数靠谱。关键那段是:
bash
docker run --name some-redis -d redis redis-server --appendonly yes
两个细节值得说清:
redis后面跟的redis-server --appendonly yes覆盖了镜像默认启动命令,把持久化从 RDB 切成 AOF。--appendonly yes打开 AOF,Redis 会把每条写命令追加到日志文件,重启后按日志重放恢复数据;镜像默认的 RDB 是定时快照,粒度粗、掉电可能丢最后一次快照之后的数据。
官方那条命令没配端口,宿主机连不上,我们改造一下再加 -p:
bash
docker run --name myredis -p 6379:6379 -d redis redis-server --appendonly yes
创建并验证
预期输出是一串容器 ID:
text
7f3c1e9a2b0d4c8e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e
立刻确认它真的在跑:
bash
docker ps
text
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f3c1e9a2b0d redis "docker-entrypoint.s..." 5 seconds ago Up 5 seconds 0.0.0.0:6379->6379/tcp myredis
验证点:STATUS 是 Up ...,PORTS 列出现 0.0.0.0:6379->6379/tcp,说明 6379 映射生效。
进入容器写数据
进入容器有两种写法,效果一样,第二种更省事。
先在 bash 里中转:
bash
docker exec -it myredis bash
进去之后容器 ID 和宿主机上看到的一致,再启 redis-cli:
bash
redis-cli
text
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set number 666
OK
127.0.0.1:6379> get number
"666"
也可以一步到位,exec 直接执行 redis-cli,不必先落到 bash:
bash
docker exec -it myredis redis-cli
text
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> get number
"666"
exec 的本质是"在容器里执行一条命令",执行 bash 是给你一个 shell,执行 redis-cli 就是直接开会话。退出也分两层:第一次 exit 退出 redis-cli,回到容器 bash;再 exit 才退回宿主机。用第二种写法只需退一次。
持久化到底有没有生效
这是练习一真正的考点。停掉容器再启动,数据还在才是 AOF 起了作用:
bash
docker stop myredis
docker start myredis
docker exec -it myredis redis-cli get number
text
"666"
stop 只是终止进程,容器的可写层还在,加上 AOF 文件也落在容器里,所以 start 之后数据能恢复。但这不等于数据安全 ------一旦 docker rm 把容器删掉,AOF 文件跟着没了。这个问题留到数据卷那节解决,这里先记住现象。
练习二:Nginx 容器,改页面再验证
这题来自讲义 2.2.3,需求是:进入 Nginx 容器,改掉首页内容,然后从容器外面看到改动。
创建并运行
bash
docker run --name mn -p 80:80 -d nginx
-p 80:80 冒号左边是宿主机端口、右边是容器端口。容器默认是隔离环境,不映射的话宿主机 80 端口访问不到里面的 Nginx。映射之后,访问宿主机 80 就等于访问容器 80。
验证:
bash
docker ps
text
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a1b2c3d4e5f nginx "/docker-entrypoint...." 3 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp mn
进入容器,定位 index.html
bash
docker exec -it mn bash
Nginx 的静态页目录是 /usr/share/nginx/html,进去看看:
bash
cd /usr/share/nginx/html
ls
text
50x.html index.html
容器里没有 vi/vim,文件怎么改
这是新手最容易卡住的地方:vi 敲进去提示 command not found。Nginx 镜像为了瘦身没装编辑器,别急着 apt install vim ,那会往容器可写层里塞一堆东西,也不符合"容器即用即弃"的思路。直接用 sed 或 echo 就地改。
用 sed -i 替换文件里的字符串:
bash
sed -i -e 's#Welcome to nginx#欢迎使用 Docker#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html
第二个 -e 是顺手补一行 UTF-8 声明,不改的话中文在浏览器里会乱码,这点很容易被忽略。
也可以整份覆盖,echo + 重定向最直接:
bash
echo '<h1>欢迎使用 Docker</h1>' > /usr/share/nginx/html/index.html
注意别写成 >>,那是追加;用 > 才是覆盖。改完先别退出,直接在容器里确认文件内容变了:
bash
cat /usr/share/nginx/html/index.html
text
<h1>欢迎使用 Docker</h1>
退出后从外部验证
bash
exit
在宿主机上不用打开浏览器,一条 curl 就能验证:
bash
curl http://localhost
text
<h1>欢迎使用 Docker</h1>
浏览器访问 http://<虚拟机IP>(例如 http://192.168.150.101)应看到同样的内容。如果浏览器还是旧页面,先强制刷新(Ctrl+F5),多数时候是缓存。
停止、重启、再验证
bash
docker stop mn
docker start mn
curl http://localhost
text
<h1>欢迎使用 Docker</h1>
改动还在,因为 start 复用同一个容器的可写层,不会重建。
删除容器
bash
docker rm mn
如果提示容器还在运行:
text
Error response from daemon: You cannot remove a running container 9a1b2c3d4e5f. Stop the container before attempting removal or force remove
先 docker stop mn 再 rm,或者直接 docker rm -f mn 强制删。
删完再 curl 一次,会发现连接失败------容器没了,端口映射自然也没了。最扎心的是:刚才改的 index.html 一起没了。 这就是下一节数据卷要解决的问题。
步骤 / 命令 / 验证点总表
把练习二的每一步压进一张表,方便对照着敲:
| 步骤 | 命令 | 验证点 |
|---|---|---|
| 创建并运行 | docker run --name mn -p 80:80 -d nginx |
输出容器 ID |
| 确认运行 | docker ps |
STATUS 为 Up,PORTS 含 0.0.0.0:80->80/tcp |
| 进入容器 | docker exec -it mn bash |
提示符变成容器内路径 |
| 定位文件 | cd /usr/share/nginx/html && ls |
看到 index.html |
| 修改文件 | sed -i ... index.html |
cat index.html 内容已变 |
| 退出 | exit |
提示符回到宿主机 |
| 外部验证 | curl http://localhost |
返回新内容 |
| 停止 | docker stop mn |
docker ps 里看不到它 |
| 重启 | docker start mn |
docker ps 里重新出现 |
| 再次验证 | curl http://localhost |
仍是新内容 |
| 删除 | docker rm mn |
docker ps -a 里消失,curl 失败 |
整个练习的操作序列
容器 mn (nginx) Docker 守护进程 宿主机终端 容器 mn (nginx) Docker 守护进程 宿主机终端 #mermaid-svg-KGFGLu4u4FiBh8AW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KGFGLu4u4FiBh8AW .error-icon{fill:#552222;}#mermaid-svg-KGFGLu4u4FiBh8AW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KGFGLu4u4FiBh8AW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KGFGLu4u4FiBh8AW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KGFGLu4u4FiBh8AW .marker.cross{stroke:#333333;}#mermaid-svg-KGFGLu4u4FiBh8AW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KGFGLu4u4FiBh8AW p{margin:0;}#mermaid-svg-KGFGLu4u4FiBh8AW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGFGLu4u4FiBh8AW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KGFGLu4u4FiBh8AW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-KGFGLu4u4FiBh8AW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-KGFGLu4u4FiBh8AW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-KGFGLu4u4FiBh8AW .sequenceNumber{fill:white;}#mermaid-svg-KGFGLu4u4FiBh8AW #sequencenumber{fill:#333;}#mermaid-svg-KGFGLu4u4FiBh8AW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-KGFGLu4u4FiBh8AW .messageText{fill:#333;stroke:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGFGLu4u4FiBh8AW .labelText,#mermaid-svg-KGFGLu4u4FiBh8AW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .loopText,#mermaid-svg-KGFGLu4u4FiBh8AW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KGFGLu4u4FiBh8AW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-KGFGLu4u4FiBh8AW .noteText,#mermaid-svg-KGFGLu4u4FiBh8AW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-KGFGLu4u4FiBh8AW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGFGLu4u4FiBh8AW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGFGLu4u4FiBh8AW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGFGLu4u4FiBh8AW .actorPopupMenu{position:absolute;}#mermaid-svg-KGFGLu4u4FiBh8AW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-KGFGLu4u4FiBh8AW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGFGLu4u4FiBh8AW .actor-man circle,#mermaid-svg-KGFGLu4u4FiBh8AW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-KGFGLu4u4FiBh8AW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker run --name mn -p 80:80 -d nginx 创建容器并启动 nginx 进程 返回容器 ID docker exec -it mn bash 在容器内启动 bash 分配交互式终端 sed -i 修改 index.html 文件写入容器可写层 exit 退出 curl http://localhost (经 80 端口映射) 返回修改后的页面 docker stop mn 终止进程, 保留可写层 docker start mn 重新启动 nginx curl http://localhost 内容依然是修改后的 docker rm mn 删除容器及可写层(改动丢失)
常见失败与排查
这几类报错几乎每个初学者都会遇到一次:
| 现象 / 报错 | 原因 | 处理 |
|---|---|---|
Conflict. The container name "/mn" is already in use |
上一次练习的容器没删 | docker rm -f mn 后重来,或换 --name |
bind: address already in use(80 端口) |
宿主机 80 被别的进程占了(如已有 Nginx) | 换端口 -p 8080:80,访问改 8080 |
Container xxx is not running |
对已停止的容器执行 exec |
先 docker start xxx |
executable file not found in $PATH |
镜像里没有 bash(alpine 系用 sh) |
换成 docker exec -it mn sh |
| 改完页面没变化 | 浏览器缓存 / 改错文件路径 | Ctrl+F5 强刷,或 cat 确认文件内容 |
| 容器起来就退出 | 镜像的默认命令不是长驻进程,或忘了 -d 之外的启动命令 |
docker ps -a 看 STATUS 是否 Exited |
You cannot remove a running container |
直接删运行中的容器 | 先 stop 或 rm -f |
补充一个小技巧:排查容器为什么起不来,先看日志而不是瞎猜。
bash
docker logs mn
加 -f 可以持续跟踪输出,跟 tail -f 一个感觉。
改过的文件去哪了
上一节反复强调的一件事:容器内的改动写在可写层 里,跟容器同生共死。stop / start 不动它,rm 把它一起删。
#mermaid-svg-Bu52XfuHUUAsNKcB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bu52XfuHUUAsNKcB .error-icon{fill:#552222;}#mermaid-svg-Bu52XfuHUUAsNKcB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bu52XfuHUUAsNKcB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bu52XfuHUUAsNKcB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bu52XfuHUUAsNKcB .marker.cross{stroke:#333333;}#mermaid-svg-Bu52XfuHUUAsNKcB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bu52XfuHUUAsNKcB p{margin:0;}#mermaid-svg-Bu52XfuHUUAsNKcB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB .cluster-label text{fill:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB .cluster-label span{color:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB .cluster-label span p{background-color:transparent;}#mermaid-svg-Bu52XfuHUUAsNKcB .label text,#mermaid-svg-Bu52XfuHUUAsNKcB span{fill:#333;color:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB .node rect,#mermaid-svg-Bu52XfuHUUAsNKcB .node circle,#mermaid-svg-Bu52XfuHUUAsNKcB .node ellipse,#mermaid-svg-Bu52XfuHUUAsNKcB .node polygon,#mermaid-svg-Bu52XfuHUUAsNKcB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bu52XfuHUUAsNKcB .rough-node .label text,#mermaid-svg-Bu52XfuHUUAsNKcB .node .label text,#mermaid-svg-Bu52XfuHUUAsNKcB .image-shape .label,#mermaid-svg-Bu52XfuHUUAsNKcB .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bu52XfuHUUAsNKcB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bu52XfuHUUAsNKcB .rough-node .label,#mermaid-svg-Bu52XfuHUUAsNKcB .node .label,#mermaid-svg-Bu52XfuHUUAsNKcB .image-shape .label,#mermaid-svg-Bu52XfuHUUAsNKcB .icon-shape .label{text-align:center;}#mermaid-svg-Bu52XfuHUUAsNKcB .node.clickable{cursor:pointer;}#mermaid-svg-Bu52XfuHUUAsNKcB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bu52XfuHUUAsNKcB .arrowheadPath{fill:#333333;}#mermaid-svg-Bu52XfuHUUAsNKcB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bu52XfuHUUAsNKcB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bu52XfuHUUAsNKcB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bu52XfuHUUAsNKcB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bu52XfuHUUAsNKcB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bu52XfuHUUAsNKcB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bu52XfuHUUAsNKcB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bu52XfuHUUAsNKcB .cluster text{fill:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB .cluster span{color:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bu52XfuHUUAsNKcB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bu52XfuHUUAsNKcB rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bu52XfuHUUAsNKcB .icon-shape,#mermaid-svg-Bu52XfuHUUAsNKcB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bu52XfuHUUAsNKcB .icon-shape p,#mermaid-svg-Bu52XfuHUUAsNKcB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bu52XfuHUUAsNKcB .icon-shape .label rect,#mermaid-svg-Bu52XfuHUUAsNKcB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bu52XfuHUUAsNKcB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bu52XfuHUUAsNKcB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bu52XfuHUUAsNKcB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker start
docker rm
挂载数据卷
镜像 nginx 只读层
容器可写层
随容器一起销毁
宿主机目录
想让它活下来,得把容器内目录挂到宿主机的数据卷上,让读写落在宿主机------操作宿主机的目录就等于操作容器内的目录,改文件也不用再进容器。数据卷的用法另开一篇讲,这里先把"容器可写层随容器销毁"这个事实记牢,它是理解数据卷的起点。
可整段复制的完整脚本
下面这个脚本把练习二从创建到验证串了一遍,变量都抽出来了,直接贴进终端执行(需要 root 或 docker 权限):
bash
#!/usr/bin/env bash
set -e
NAME=mn
HOST_PORT=80
CONT_HTML=/usr/share/nginx/html/index.html
# 1. 清理可能残留的同名容器
docker rm -f "$NAME" 2>/dev/null || true
# 2. 创建并运行 nginx 容器,映射宿主机 80 到容器 80
docker run --name "$NAME" -p "${HOST_PORT}:80" -d nginx
# 3. 确认容器在运行
docker ps --filter "name=${NAME}"
# 4. 进入容器,确认 index.html 存在(-T 不分配终端,适合脚本里管道)
docker exec "$NAME" ls /usr/share/nginx/html
# 5. 就地修改页面内容(容器内无 vi,用 echo 覆盖)
docker exec "$NAME" sh -c "echo '<h1>欢迎使用 Docker</h1>' > ${CONT_HTML}"
# 6. 从宿主机验证改动
sleep 1
curl "http://localhost:${HOST_PORT}"
# 7. 停止 -> 重启 -> 再验证,改动仍在
docker stop "$NAME"
docker start "$NAME"
sleep 1
curl "http://localhost:${HOST_PORT}"
# 8. 删除容器(改过的文件随之消失)
docker rm -f "$NAME"
脚本版和交互版的区别:脚本里 docker exec 不带 -it,因为不需要交互终端;带 -it 在非交互环境下反而会报"the input device is not a TTY"。
命令速览
| 场景 | 命令 |
|---|---|
| 创建并运行(后台 + 端口映射) | docker run --name mn -p 80:80 -d nginx |
| 创建带 AOF 的 Redis | docker run --name myredis -p 6379:6379 -d redis redis-server --appendonly yes |
| 查看运行中的容器 | docker ps |
| 查看所有容器 | docker ps -a |
| 进入容器(shell) | docker exec -it mn bash |
| 进入容器(直接执行命令) | docker exec -it myredis redis-cli |
| 非交互执行命令 | docker exec mn sh -c '<命令>' |
| 查看日志 | docker logs -f mn |
| 停止 / 启动 | docker stop mn / docker start mn |
| 删除 | docker rm mn(运行中加 -f) |
官方文档
总结
- 两道练习覆盖了容器的完整生命周期:
run → exec → 改内容 → 外部验证 → stop → start → 再验证 → rm。 - Nginx 里没有 vi 是常态,
sed -i或echo >才是容器里的正常改文件方式,别为了编辑器把镜像撑大。 stop/start复用同一个容器,改动保留;rm连同可写层一起销毁,改动全没------这个规律解释了为什么需要数据卷。- 报错基本集中在四处:容器名冲突、端口被占、对停止态容器 exec、镜像里没有 bash。记住这几条能省很多时间。
- 验证永远比命令重要:
docker ps看状态、cat看文件、curl看结果,每一步都留个可见的检查点。