raming

常用的 Docker 命令

代码27 分钟

日常那几十条按「要办的事」分组:先起容器、再看它活着没有、进去敲两下、管镜像、护住数据、Compose、清垃圾,一条命令一行作用,外加几组真的会敲出来的组合。

Docker 的命令不难,难的是同一个动词在镜像、容器、卷、网络四个对象上各有一套,背下来也会串。所以这份清单不按字母排,按"我现在要办什么事"排:先起容器,再看它活着没有,看不对劲就进去敲两下,最后管镜像和数据。参数记不住就敲 <命令> --help,那一屏就是全集,比翻笔记快。

其中有一组是「几组我会敲出来的形状」——单条命令记不住没关系,那几段组合记住了,单条就会顺出来。

先分清四个对象

  • 镜像 image —— 只读的模板,一层一层叠出来的;它有版本号(tag),没有状态
  • 容器 container —— 镜像跑起来的那个实例,有自己的 ID、状态、文件系统上层,删掉就没了
  • 卷 volume —— 专门放数据的、不由容器生命周期管的地方;数据库的落盘应该在这儿
  • 网络 network —— 容器之间怎么互相找到;同一张网络里可以用服务名当域名

命令的写法是 docker <对象> <动作>,比如 docker image ls、docker container rm。早先的简写(docker ps、docker images)都还能用,下面的清单里两种都按最常见那种写。

装完先跑这几条

  • docker version —— 客户端和守护进程的版本;两边差太远会有些命令报看不懂
  • docker info —— 一整屏运行环境:存储驱动、cgroup 版本、代理、日志驱动、跑着几个容器
  • docker info --format '{{.ServerVersion}}' —— 只取一个字段,-f 吃的是 Go 模板
  • docker compose version —— Compose 是插件,不在 daemon 里,单独确认它在
  • docker context ls —— 当前 -h 会连哪台机器;本地和远端切换就靠这个
  • docker events —— 实时打 daemon 上发生的事,查"谁在起容器"时有用
  • docker --help / docker ps --help —— 参数全集都在这一屏里,别背

起一个容器:run 的那几个必带参数

docker run = 拉镜像(没有就拉)+ 建容器 + 启动,三段合一,所以参数最多。

  • -d —— 后台跑,只回一个容器 ID;不加就占住终端,Ctrl-C 直接把容器停掉
  • --name blog —— 给它一个你能记住的名字,后面所有命令都用它而不是那串哈希
  • -p 8080:80 —— 把宿主端口映射到容器端口,冒号左边是外面访问的、右边是容器里监听的
  • -p 127.0.0.1:8080:80 —— 只绑本机回环,不让它露在局域网口上;反代在前面时该这么写
  • -p 80 —— 只写一边,随机给一个宿主端口,用 docker port 容器 查分到哪个
  • -v /data:/var/www:ro —— 挂宿主目录进去,ro 是只读
  • -v mydata:/var/lib/postgresql/data —— 左边不是路径就是一个卷,数据跟着卷活
  • -e TZ=Asia/Shanghai —— 传环境变量;密钥别写在这里,会留在 docker inspect 里
  • --env-file .env —— 一整个文件灌进去,参数少一半
  • --restart unless-stopped —— 开机自启、崩了自动拉起;手动 stop 的那次不复活
  • --restart always —— 同上,但手动 stop 也会在 daemon 重启时把它拉起来
  • --rm —— 退出就删容器,跑一次性任务时用,省得堆一地尸壳
  • -it —— 分配终端并进入交互,跑 shell 必备;-d 和 -it 不一起用
  • --memory 512m / --cpus 1 —— 限额;没有这行,一个失控的进程能把整台机器拖走
  • --log-opt max-size=10m --log-opt max-file=3 —— 日志封顶,默认是无限增长的
  • --add-host 名字:IP —— 往容器的 /etc/hosts 里塞一行
  • --cap-drop ALL --cap-add NET_ADMIN —— 先把能力全去掉,再按这条服务真正需要的一项项给回来
  • --privileged —— 别顺手加。它把宿主设备、内核模块、全部能力都给了容器
  • --device /dev/ttyUSB0 —— 直通设备;--gpus all 要另装 NVIDIA Container Toolkit
  • -w /app 镜像 命令 —— 指定工作目录、覆盖启动命令
  • --entrypoint sh 镜像 -c '命令' —— 换掉入口点,用来在镜像里跑一条不相干的检查

一条常见的形状:

bash
docker run -d --name blog \
-p 127.0.0.1:8080:80 \
-v myblog:/app/data \
-e TZ=Asia/Shanghai \
--restart unless-stopped \
--log-opt max-size=10m --log-opt max-file=3 \
nginx:1.27

看它在不在、在干什么

  • docker ps —— 正在跑的
  • docker ps -a —— 含已退出的;"我明明起了怎么没起来"先看这一条
  • docker ps -a --filter status=exited —— 只看退出的
  • docker ps --format '{{.Names}}\t{{.Status}}\t{{.Ports}}' —— 想要什么样的表头自己拼
  • docker ps -q —— 只出 ID,配合 xargs 批量操作
  • docker port blog —— 这个容器把哪些端口映射到了宿主
  • docker logs blog —— 打日志;容器停了也能看
  • docker logs -f --tail 200 blog —— 只从最近两百行开始追,不要一上来就从头灌
  • docker logs --since 30m blog —— 最近三十分钟;--until 同样能配
  • docker inspect blog —— 一大坨 JSON,什么都在里面:IP、挂载、退出码、启动参数
  • docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}}' blog —— 崩溃排查先问这两个
  • docker stats --no-stream —— 每个容器现在的 CPU / 内存 / 网络 / 块设备
  • docker top blog —— 容器里跑了哪些进程(宿主上看的,不需要进去)
  • docker diff blog —— 这个容器在镜像之上改了哪些文件;找"它到底往哪写"最快

进容器里敲命令

  • docker exec -it blog sh —— 进去。先在镜像里试 sh 再试 bash:alpine 系没有 bash
  • docker exec -u root blog sh —— 换个用户进去,很多镜像默认不是 root
  • docker exec blog 命令 —— 不交互,只拿输出,脚本里用这一条
  • docker exec -w /app blog ls —— 指定进去之后站在哪个目录
  • docker attach blog —— 接回容器的标准输入输出;Ctrl-P Ctrl-Q 是脱离, Ctrl-C 会真的把进程打断,别在跑前台任务时用
  • docker cp blog:/etc/nginx/nginx.conf ./ —— 从容器拷出来,反向也一样;容器停了也能拷
  • docker commit blog blog:debug —— 把当前容器封成一个新镜像。排查时临时用, 别拿它当部署手段——那样 Dockerfile 和镜像就分家了

停、启、删

  • docker stop blog —— 发 SIGTERM,等十秒还没退就补 SIGKILL
  • docker stop -t 30 blog —— 给它三十秒优雅退出(跑数据库的该加大这个数)
  • docker kill blog —— 直接 SIGKILL,没有优雅可言
  • docker kill --signal=HUP blog —— 只发一个信号不停容器,比如让 nginx 重载配置
  • docker start blog —— 起已经建好的容器,参数不再变;-i 是把标准输入一直接上
  • docker restart blog —— 停再起;改环境变量没用,那些在创建时就定了
  • docker pause / unpause —— 用 cgroup freezer 冻住 / 解冻,进程还活着但不跑
  • docker rename blog blog-old —— 改名,不影响它的 ID
  • docker wait blog —— 阻塞到它退出,打印退出码,脚本里等一个任务时用
  • docker rm blog —— 删容器;正在跑得先 stop,或者 docker rm -f blog
  • docker container prune —— 把所有已退出的容器一次清掉,会先问你一句

删容器不会删卷,也不会删镜像。数据在不在,看下一组。

镜像:拉、看、造、推

  • docker pull nginx:1.27 —— 拉这个 tag;不写 tag 就是 latest,而它只是一个默认标签名, 不等于"最新一版"——要版本就写版本
  • docker images —— 本地有哪些镜像,占多少空间
  • docker images -f dangling=true —— 没有 tag 的孤儿层,构建换了几轮之后一堆这种
  • docker image inspect nginx:1.27 —— 看它的层、入口点、暴露端口、环境变量
  • docker history nginx:1.27 --no-trunc —— 每一层是哪条指令来的、各占多大; 想知道镜像为什么这么肥就查这个
  • docker rmi nginx:1.27 —— 删镜像;有容器在用会删不掉,-f 是拆标签不是真删
  • docker build -t blog:1.0 . —— 用当前目录的 Dockerfile 构建,最后一个点是构建上下文
  • docker build --no-cache -t blog:1.0 . —— 跳过缓存重来,改了 COPY 却没生效时用
  • docker build -f Dockerfile.dev -t blog:dev . —— 换一份 Dockerfile
  • docker build --build-arg VERSION=1.2.3 -t blog:1.2.3 . —— 传构建参数
  • docker tag 旧名 注册表/名:tag —— 打上要推出去的那个名字
  • docker login —— 推之前先登录。匿名拉取会被限流,症状是 429 Too Many Requests, 登录后配额宽得多
  • docker push 注册表/名:tag —— 推上去;推一个别人已经推过的官方镜像会直接拒
  • docker save -o nginx.tar nginx:1.27 —— 把镜像导出成 tar(带层、带元数据)
  • docker load -i nginx.tar —— 从 tar 装回镜像;内网机器之间搬镜像走这一对
  • docker export 容器 > fs.tar —— 导出的是容器文件系统,没有层也没有启动命令, 和 save 不是一回事,别混用
  • docker image prune -a —— 删掉没有任何容器在用的镜像,会先列一遍再问

跨架构那一条得单独记:

bash
docker buildx build --platform linux/amd64,linux/arm64 \
-t 注册表/blog:1.0 --push .

--push(或者 --output type=registry)是必需的——多平台镜像没法落到本地那一份 docker images 里。

数据与卷:容器删了东西还在

  • docker volume ls —— 有哪些卷
  • docker volume inspect mydata —— 它真实落在宿主哪个目录(Mountpoints 那一行)
  • docker volume create mydata —— 先建再挂,参数能写在创建时
  • docker volume rm mydata —— 删卷。这一下才是真的把数据删了,删容器不会
  • docker volume prune —— 清没人挂的卷。新版本默认只动匿名卷,具名那些要加 --all 才被扫进去——反过来说,忘了名字的卷就是被这一条带走的
  • --mount type=volume,source=mydata,target=/data —— 和 -v 同一件事,写法啰嗦但参数不会串
  • --mount type=tmpfs,destination=/app/tmp —— 只在内存里,重启就没,适合临时缓存

备份和恢复对称着记最不容易错:

bash
# 备份:卷挂到 /from,当前目录挂到 /to,写一个带日期的包
docker run --rm -v mydata:/from -v "$PWD":/to \
alpine tar czf /to/mydata-$(date +%F).tgz -C /from .
# 恢复:方向反过来,解到那个卷里
docker run --rm -v mydata:/to -v "$PWD":/from \
alpine sh -c 'tar xzf /from/mydata-2026-10-26.tgz -C /to'

网络:谁找得到谁

  • docker network ls —— 有哪些网络;bridge 是默认那张
  • docker network inspect bridge —— 谁在这张网上、各自的 IP
  • docker network create blog-net —— 建一张自己的,容器之间就能用名字互访
  • docker network connect blog-net blog —— 把一个已经跑着的容器加进去,不用重建
  • docker network disconnect blog-net blog —— 反过来
  • docker network rm blog-net —— 还有容器挂着就删不掉
  • docker run --network blog-net ... —— 起的时候指定
  • docker run --network host ... —— 直接用宿主网络,没有端口映射这回事,也不隔离
  • docker run --network container:nginx ... —— 和某个容器共用网络栈,两个进程共享一个 IP, 边车(sidecar)模式就是这个
  • docker run --dns 1.1.1.1 ... —— 容器里的解析不对时先试这个
  • Compose 里同一个 services 下的服务默认就在同一张网上,服务名就是域名

Compose:一个文件管一组

docker compose(中间一个空格)是现在的写法;带横杠的 docker-compose 是退役的 v1, 新项目里别再敲它。文件名 compose.yml / compose.yaml / docker-compose.yml 都认, 新项目就写 compose.yml。

最小的形状就这么点东西,一个服务一段:

yaml
services:
web:
image: nginx:1.27
ports:
- '127.0.0.1:8080:80'
volumes:
- ./dist:/usr/share/nginx/html:ro
restart: unless-stopped

下面这些命令都是围着这一份文件转的。

  • docker compose up -d —— 按文件建好并后台起;改了配置也常敲这条
  • docker compose up -d --build —— 起之前重新构建本地那个服务
  • docker compose up -d --force-recreate —— 参数没变但想彻底换个新容器
  • docker compose down —— 停并删掉这一组容器和网络,卷留着
  • docker compose down -v —— 连卷一起删,数据就没了;这行不该出现在生产机上
  • docker compose down --remove-orphans —— 把文件里已经不再列出、但还活着的容器收掉
  • docker compose ps —— 这一组现在的状态
  • docker compose logs -f web —— 追某一个服务的日志;不写服务名就是全部
  • docker compose logs --since 10m --tail 100 —— 别每次都从头灌
  • docker compose restart web —— 只重起那一个
  • docker compose pull && docker compose up -d —— 升级的标准两步
  • docker compose build --no-cache web —— 重新构建,不吃缓存
  • docker compose config —— 把最终合成的配置打出来(变量已代入、默认值已补齐); 怀疑 .env 没生效就先看这个
  • docker compose config --quiet —— 只校验语法,不合法就非零退出,适合放进脚本
  • docker compose exec web sh —— 进某个服务的容器
  • docker compose run --rm web 命令 —— 用同一套配置临时跑一个一次性任务(迁移、初始化)
  • docker compose top —— 这一组里所有进程
  • docker compose stop / start / kill —— 三种力度:优雅停、起回来、直接杀
  • docker compose rm -sf —— 删掉这一组里已经停的容器
  • docker compose scale web=3 —— 副本数;端口有写死的宿主端口时会冲突,得改成范围写法

我实际会写的那一份,比最小形状多了这几样:具名卷、健康检查、显式网络、日志封顶、 启动顺序。密钥走 env_file,不写进这一份文件里(environment 的值会留在 docker inspect)。

yaml
services:
api:
build: ./api
init: true
env_file: .env
environment:
TZ: Asia/Shanghai
volumes:
- apidata:/data
stop_grace_period: 30s
healthcheck:
test: ['CMD-SHELL', 'wget -qO- http://127.0.0.1:8080/healthz || exit 1']
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
logging:
driver: json-file
options:
max-size: '10m'
max-file: '3'
networks: [backend]
web:
image: nginx:1.27
ports:
- '127.0.0.1:8080:80'
volumes:
- ./dist:/usr/share/nginx/html:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
networks: [frontend, backend]
networks:
frontend:
backend:
internal: true
volumes:
apidata:

两处要盯的:

  • internal: true 那张网出不去外网——挂在上面的服务互相能通、但访问不了外面,也出不去。 数据库这类"只被本组里的人连"的放这儿正合适,别顺手把自己要拉包的服务也挂上去。
  • healthcheck 那条命令必须在那个镜像里真跑得通。上面写的是带 busybox(有 wget)的 自己 build 出来的服务;换个基础镜像之前先量一下里面有什么: docker exec -it 容器 sh -c 'command -v wget curl'。两样都没有时,宁可写 disable: true 把健康检查关掉,也别留一条永远失败的检查——那会把 depends_on 的 condition: service_healthy 卡住,整组起不来。

清垃圾:先看用量,再动手

容器越跑越多、盘越用越满,八成是这三样堆出来的:退出的容器、没标签的镜像、构建缓存。

  • docker system df —— 分类统计镜像 / 容器 / 卷 / 构建缓存各占多少
  • docker system df -v —— 展开到每一条,找出"那个 3G 的镜像是谁"
  • docker container prune —— 清已退出的容器
  • docker image prune —— 只清孤儿层(没标签的);加 -a 就清所有没被容器用着的镜像
  • docker builder prune —— 清构建缓存,构建盘满通常是它
  • docker volume prune —— 清没人挂的卷,这一下就包括那些你忘了名字的数据卷
  • docker system prune —— 一次清掉停用的容器 + 悬空镜像 + 构建缓存 + 没人挂的网络,不含卷
  • docker system prune -a --volumes —— 全清,包括卷。除非你确定这台机器上没有需要留着的数据, 别敲这条;它不会提醒第二遍

安全的顺序是:先 df,再单独 container prune,看还剩多少,最后才决定要不要动 image prune -a。

bash
docker system df
docker container prune
docker image prune
docker builder prune

几组我会敲出来的形状

一、拿一个临时容器试件事,试完不留痕迹:

bash
docker run --rm -it alpine:3.20 sh

二、只看一眼某个镜像的默认启动命令和环境变量,不用真的起:

bash
docker inspect --format '入口点 {{.Config.Entrypoint}} 命令 {{.Config.Cmd}}' nginx:1.27
docker inspect --format '{{json .Config.ExposedPorts}}' nginx:1.27

三、容器起不来,三步定位:

bash
docker ps -a --filter name=blog --format '{{.Status}}'
docker logs --tail 100 blog
docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}} {{.RestartCount}}' blog

四、把跑着的容器和宿主端口对上号,查"这个端口被谁占了":

bash
docker ps --format '{{.Names}}: {{.Ports}}'
ss -lntp | grep 8080

五、升级一个 Compose 服务,出问题能立刻退回:

bash
docker compose logs --tail 50 web > /tmp/web-before.log
docker compose pull web
docker compose up -d web
docker compose ps

六、改完 Dockerfile 确认缓存真的没骗你:

bash
docker build --no-cache -t blog:test .
docker history blog:test --no-trunc | head

权限那件事

docker 这个命令连的是本地的守护进程 socket,而 socket 默认只有 root 可读。把自己加进 docker 组等于给了这台机器的 root 权限——组里每一个人都能挂宿主根目录进容器,然后随意读写。共用机器时这一点要想清楚,宁可留着 sudo。

  • sudo usermod -aG docker $USER —— 加组,然后重新登录才生效(或者 newgrp docker)
  • getent group docker —— 看组里现在都有谁
  • docker info --format '{{.SecurityOptions}}' —— 守护进程开了哪些安全选项

忘了参数的时候

  • docker ps --help —— 每一层命令都带一份完整参数说明,比搜得快
  • docker compose config —— 想知道 compose 文件最终变成了什么
  • docker inspect —— 想知道某个对象现在是什么状态
  • docker events —— 想知道刚才那一下到底发生了什么
  • journalctl -u docker --since "10 min ago" —— 守护进程自己的日志;容器起不来的原因有时在这儿而不在 docker logs 里

命令串得再熟,也只是把"该问 daemon 的事"问出来。真正少踩坑靠的是前面那几条习惯:数据放卷里、端口只绑回环、日志设上限、清理之前先看一眼 docker system df。

标签:DockerLinux