ElIauk

Docker 镜像与容器常用命令

镜像和容器的关系 刚接触 Docker 时,最容易混淆的是"镜像"和"容器"。可以这样理解:镜像是只读的模板,容器是镜像跑起来之后的实例。一个镜像可以启动多个容器,每个容器在镜像之上加一层可写层,删掉容器,这层数据就没了。 日常操作基本围绕这两类对象:拉镜像、看镜像、删镜像;起容器、看日志、进容器、

评论
Docker 镜像与容器常用命令

镜像和容器的关系

刚接触 Docker 时,最容易混淆的是"镜像"和"容器"。可以这样理解:镜像是只读的模板,容器是镜像跑起来之后的实例。一个镜像可以启动多个容器,每个容器在镜像之上加一层可写层,删掉容器,这层数据就没了。

日常操作基本围绕这两类对象:拉镜像、看镜像、删镜像;起容器、看日志、进容器、停容器。命令不多,但参数组合很多,踩坑主要集中在这几组参数上。

镜像相关命令

# 拉取指定版本,生产环境不要用 latest
docker pull nginx:1.24

# 查看本地镜像
docker images

# 给镜像打标签,准备推送到私有仓库
docker tag nginx:1.24 registry.example.com/library/nginx:1.24

# 查看镜像的构建历史
docker history nginx:1.24

# 导出和导入,适合离线环境
docker save -o nginx-1.24.tar nginx:1.24
docker load -i nginx-1.24.tar

# 删除镜像,先确认没有容器在使用
docker rmi nginx:1.24

# 清理悬空镜像
docker image prune -f

镜像由一层层只读层叠加而成,docker history 能看到每一层对应的构建指令。层数越多、体积越大,拉取和启动越慢,这也是后面写 Dockerfile 时要合并 RUN 指令、清理缓存的原因。

容器相关命令

最常用的是 docker run,参数决定容器怎么跑:

参数 作用 示例
-d 后台运行 docker run -d ...
--name 指定容器名 --name app
-p 端口映射 -p 8080:8080
-v 目录挂载 -v /data/logs:/app/logs
-e 环境变量 -e TZ=Asia/Shanghai
--restart 重启策略 --restart unless-stopped
--network 指定网络 --network app-net
--rm 退出后自动删除 适合一次性任务

一个完整的启动示例:

docker run -d --name app \
  -p 8080:8080 \
  -v /data/app/app.jar:/app/app.jar \
  -v /data/app/logs:/app/logs \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e TZ=Asia/Shanghai \
  --restart unless-stopped \
  eclipse-temurin:17-jre-jammy \
  java -jar /app/app.jar

容器起来之后,常用的排查命令:

docker ps -a                          # 包括已退出的容器
docker logs -f --tail 200 app         # 跟踪日志
docker exec -it app sh                # 进入容器
docker inspect -f '{{.State.Status}}' app
docker stats --no-stream              # 资源占用
docker stop app && docker rm app      # 停止并删除

容器的生命周期是 created → running → paused → exited → removed,退出不等于删除,日志和数据层还在,docker ps -a 能看到,docker rm 才真正清掉。

常见坑

1. 容器启动后立刻退出

docker run 返回了容器 ID,docker ps 却看不到。先看退出码:

docker ps -a
docker logs app

退出码 0 通常是容器里的主进程执行完就结束了,比如跑了一个一次性脚本;退出码 1 多半是命令或配置错误;退出码 137 一般是被 OOM 杀掉或收到 SIGKILL;143 是正常的 SIGTERM 停止。Docker 要求容器有一个前台进程,后台运行的进程撑不住容器。

2. 端口被占用

bind: address already in use 说明宿主机端口被别的进程占了。ss -lntp | grep 8080 找到占用者,换端口或者停掉旧进程。注意 -p 8080:8080 左边是宿主机端口,右边是容器端口,别写反。

3. 挂载目录权限不对

容器里的进程以非 root 用户运行时,往宿主机挂载目录写文件会报 Permission denied。要么调整宿主机目录权限,要么在镜像里创建对应用户并设置 UID。反过来,容器用 root 跑,写出来的文件在宿主机上也是 root 所有,运维清理时很麻烦。

4. 时区是 UTC

大多数基础镜像默认 UTC,日志时间比北京时间少 8 小时。启动时加 -e TZ=Asia/Shanghai,或者在 Dockerfile 里设置。Java 应用还可以加 -Duser.timezone=Asia/Shanghai。

5. 数据没有持久化

把 MySQL、Redis 的数据目录放在容器可写层里,删容器数据就没了。数据库类容器必须挂载数据卷,并且确认备份策略覆盖到卷。

6. 一直用 latest 标签

latest 会随仓库更新而变化,今天拉的和下周拉的可能是两个版本。生产环境固定到具体版本号或 digest。

7. 磁盘被日志和悬空镜像占满

Docker 的日志默认是 json-file 驱动,不限制大小会一直涨。可以在 /etc/docker/daemon.json 里配置 max-size 和 max-file。另外,频繁构建会留下大量悬空镜像,定期执行 docker system df 看看占用,再用 docker system prune 清理。

8. 进了容器改配置,重启就没了

容器可写层的修改不会保留到镜像里,重建容器就丢失。配置应该通过环境变量、挂载文件或者重新构建镜像来管理。

排查步骤

容器异常时,按这个顺序看:

  1. docker ps -a 确认状态和退出码;
  2. docker logs --tail 200 <容器> 看应用日志;
  3. docker inspect <容器> 看挂载、环境变量、网络配置;
  4. docker exec -it <容器> sh 进去确认进程、端口、文件是否存在;
  5. docker stats 看 CPU、内存是否触顶;
  6. 宿主机上 df -h、dmesg | tail 看磁盘和 OOM 记录。

大部分"容器起不来"的问题,在前两步就能定位。真正需要进容器排查的,通常是网络和文件权限问题。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者