Skip to content

镜像与容器:run/exec/logs 的高频组合 ​

docker run 有五十多个参数,日常只用十二个。这节把常用组合和退出码讲清楚——后者是排障的第一手证据。

场景 ​

拿到一个镜像要跑起来:定端口、挂目录、传环境变量、限资源、配重启策略。跑起来之后要能看日志、进容器、查状态。

一、最小可用 ​

bash
docker run -d --name app -p 8080:8080 myapp:1.0.0
docker ps
text
CONTAINER ID   IMAGE          COMMAND               CREATED         STATUS         PORTS                    NAMES
a3f8c2e91b04   myapp:1.0.0    "java -jar /app/..."  5 seconds ago   Up 4 seconds   0.0.0.0:8080->8080/tcp   app

-d 后台运行。不加 -d 会占住当前终端,Ctrl+C 容器就停——这是"容器起来就退出"之外第二常见的误操作。

二、生产常用组合 ​

bash
docker run -d \
  --name order-api \
  --restart unless-stopped \
  -p 127.0.0.1:8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e JAVA_OPTS='-Xms1g -Xmx1g -XX:+UseG1GC' \
  -v /data/order-api/logs:/app/logs \
  -v /data/order-api/config:/app/config:ro \
  --memory 2g --memory-swap 2g --cpus 2 \
  --log-opt max-size=50m --log-opt max-file=3 \
  myapp:1.0.0
参数作用说明
--restart unless-stopped崩溃/重启后自动拉起手动 stop 的不拉起
-p 127.0.0.1:8080:8080只监听本机前面有 Nginx 时必写,避免绕过代理直连
-e KEY=VALUE环境变量密码别写在这里,见下方 warning
-v 宿主:容器:ro挂载,只读配置文件用 ro,容器改不坏宿主机
--memory内存上限超过会被 OOMKilled
--memory-swap内存+swap 总上限等于 --memory 时禁用 swap
--cpus 2CPU 配额等价 2 核,可写小数
--log-opt单容器日志轮转覆盖 daemon 全局设置

密码不要用 -e 传

docker inspect order-api 会把环境变量原样打出来,ps -ef 也能看到部分。密码用:

  • 文件挂载:-v /etc/app/secret:/app/secret:ro
  • Swarm/K8s 的 secret 机制
  • 应用启动时从配置中心拉

-e 只适合非敏感配置(profile、日志级别、时区)。

重启策略对比:

策略容器退出后daemon 重启后手动 stop 后
no(默认)不重启不重启不重启
on-failure[:N]非 0 退出才重启,最多 N 次不重启不重启
always总是重启重启重启(坑)
unless-stopped总是重启重启不重启

生产用 unless-stopped。always 的问题是:你手动 docker stop 后重启 daemon,它自己又起来了,排查问题时很迷惑。

三、退出码是最好的线索 ​

bash
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
text
NAMES        STATUS                       IMAGE
order-api    Up 3 hours                    myapp:1.0.0
old-worker   Exited (137) 2 hours ago      worker:0.9.1
batch-job    Exited (0) 2 hours ago        batch:1.2.0
broken       Exited (127) 10 minutes ago   test:latest
退出码含义常见原因
0正常结束一次性任务跑完(正常)
1应用自身报错配置错误、启动异常
125Docker 本身出错docker run 参数写错
126命令不可执行脚本没执行权限,或解释器不对
127命令找不到ENTRYPOINT/CMD 写错,镜像里没这个二进制
137收到 SIGKILL(128+9)被 OOM killer 杀,或 docker kill
139段错误(128+11)应用崩溃(JNI/本地库问题)
143收到 SIGTERM(128+15)正常的 docker stop 或 K8s 驱逐

137 是最需要警惕的。确认是否 OOM:

bash
docker inspect -f '{{.State.OOMKilled}} {{.State.ExitCode}} {{.HostConfig.Memory}}' old-worker
text
true 137 1073741824

OOMKilled=true + 上限 1GB → 内存不够。

bash
dmesg -T | grep -i 'oom.*docker\|killed process'
text
[Tue Oct 27 18:22:41 2026] Memory cgroup out of memory: Killed process 28441 (java) total-vm:2143212kB, anon-rss:1048420kB

对策:调大 --memory,或用 -XX:MaxRAMPercentage=70 让 JVM 自己收敛到容器上限的 70%。

四、看日志 ​

bash
docker logs --tail 200 -f --timestamps order-api
docker logs --since 30m --until 10m order-api
docker logs 2>&1 | grep -i -E 'error|exception' | tail -20
text
2026-10-27T20:41:02.114Z INFO  [main] Started OrderApiApplication in 8.421 seconds
2026-10-27T20:41:05.882Z INFO  [http-nio-8080-exec-1] GET /health 200 3ms
  • --timestamps 加时间戳,容器时区不对时尤其有用。
  • docker logs 只输出 stdout/stderr。应用如果把日志写到文件,docker logs 是空的——要么改成输出到 stdout,要么 docker exec 进去看文件。

日志撑爆磁盘的排查:

bash
du -sh /data/docker/containers/*/*-json.log | sort -h | tail -5
text
1.2G	/data/docker/containers/a3f8c2e91b04.../a3f8c2e91b04-json.log

当场清空(不用重启容器):

bash
truncate -s 0 /data/docker/containers/a3f8c2e91b04.../a3f8c2e91b04-json.log

truncate 是唯一安全的清理方式

rm 掉 json.log 后,Docker 仍持有该文件的句柄,磁盘空间不会释放(du 显示已删但 df 不减)。必须 truncate。

五、进容器 ​

bash
# 交互式 shell
docker exec -it order-api sh

# 用 root 进(镜像默认非 root 时)
docker exec -it -u root order-api sh

# 执行单条命令
docker exec order-api sh -c 'ls -l /app && echo $JAVA_OPTS'

# 看进程
docker exec order-api ps -ef
docker top order-api
text
PID                 USER                TIME                COMMAND
28441               app                 0:42                java -jar /app/app.jar

解释:

  • docker exec 起的是新进程,它和主进程共享命名空间,但生命周期独立(退出不影响容器)。
  • docker top 显示的是容器内视角的进程(PID 1 是主进程)。
  • 镜像基于 distroless/scratch 时没有 shell,docker exec sh 会报 executable file not found。此时用:
bash
docker inspect -f '{{.Config.Entrypoint}} {{.Config.Cmd}}' order-api
docker cp order-api:/app/config/app.yml ./app.yml     # 拷出来看
docker run --rm --pid=container:order-api --network=container:order-api \
  -it nicolaka/netshoot sh                            # 借一个带工具的容器进去

--pid=container:xxx + --network=container:xxx 让临时容器共享目标容器的进程和网络命名空间,等于"带工具箱进去看"。

六、查状态 ​

bash
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}} {{.RestartCount}} {{.State.StartedAt}}' order-api
text
running 0 false 0 2026-10-27T20:41:02.114Z

常用格式化查询:

bash
# IP
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' order-api

# 环境变量
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' order-api

# 挂载
docker inspect -f '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} ({{.RW}}){{println}}{{end}}' order-api

# 资源限制
docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}} {{.HostConfig.RestartPolicy.Name}}' order-api
text
172.18.0.4

SPRING_PROFILES_ACTIVE=prod
JAVA_OPTS=-Xms1g -Xmx1g -XX:+UseG1GC
PATH=/usr/local/sbin:...

bind /data/order-api/logs -> /app/logs (true)
bind /data/order-api/config -> /app/config (false)

2147483648 2000000000 unless-stopped

实时资源:

bash
docker stats --no-stream
text
CONTAINER ID   NAME        CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O       PIDS
a3f8c2e91b04   order-api   12.41%    1.312GiB / 2GiB       65.60%    1.24GB / 892MB    0B / 12.4MB     48

MEM USAGE / LIMIT 接近 100% 就是在 OOM 边缘。JVM 应用要留 30% 给堆外。

七、镜像操作 ​

bash
docker images
docker history myapp:1.0.0 --no-trunc | head -10
docker image inspect myapp:1.0.0 -f '{{.Size}} {{.Config.User}} {{json .Config.ExposedPorts}}'
text
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
myapp        1.0.0     7d3c1e9a2b44   2 hours ago     312MB
myapp        latest    7d3c1e9a2b44   2 hours ago     312MB
nginx        alpine    a8b1c2d3e4f5   2 weeks ago     42.6MB

IMAGE          CREATED         CREATED BY                                      SIZE
7d3c1e9a2b44   2 hours ago     CMD ["java" "-jar" "/app/app.jar"]              0B
<missing>      2 hours ago     ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]     4.1kB
<missing>      2 hours ago     COPY /app/app.jar /app/app.jar                  98MB
<missing>      2 hours ago     RUN /bin/sh -c apt-get update && apt-get ...    42MB

docker history 每行是一层。<missing> 是中间层(被后续层覆盖或来自其他镜像)。

清理:

bash
docker image prune              # 悬空镜像
docker image prune -a           # 所有没被容器使用的镜像
docker rmi myapp:0.9.1
docker rmi -f $(docker images -f 'dangling=true' -q)

八、拷贝与快照 ​

bash
# 容器 <-> 宿主机
docker cp order-api:/app/logs/app.log ./app.log
docker cp ./app.yml order-api:/app/config/app.yml

# 注意:修改容器内文件后 reinspect 才能看到差异
docker diff order-api
text
C /app/config
A /app/config/app.yml
D /app/logs/old.log

A = 新增,C = 修改,D = 删除。

不要用 docker commit 做镜像

docker commit 会把容器当前状态打成一个新镜像,看起来很方便,但:

  • 无法复现(不知道之前执行了什么)。
  • 镜像只增不减(历史层全带着)。
  • 没有构建记录、没有版本管理。

临时验证可以,绝不能用于发布。要改镜像就改 Dockerfile。

九、容器生命周期 ​

bash
docker stop order-api      # SIGTERM,默认等 10 秒后 SIGKILL
docker stop -t 30 order-api
docker kill order-api      # 直接 SIGKILL(= 退出码 137)
docker restart order-api
docker rm order-api        # 只能删已停止的
docker rm -f order-api     # 强制删(先 kill)
docker rm -f $(docker ps -aq)   # 删所有容器,危险

docker stop 的 10 秒默认值对 Java 应用往往不够(要等 Spring 优雅停机)。两个办法:

bash
docker stop -t 30 order-api
dockerfile
STOPSIGNAL SIGTERM

应用侧配合:

yaml
spring:
  lifecycle:
    timeout-per-shutdown-phase: 25s
server:
  shutdown: graceful

十、踩过的坑 ​

现象根因处置
容器刚起就 Exited (0)主进程是后台任务/脚本执行完就退前台运行,或 tail -f /dev/null 临时占位
Exited (127)CMD 里的命令不存在docker inspect 看 Entrypoint/Cmd,确认镜像内容
Exited (137)OOM 或 docker kill查 OOMKilled 与 dmesg
docker logs 空白应用写文件不写 stdout改日志配置,或 exec 进去看
端口冲突宿主端口被占ss -lntp | grep 8080
stop 要等 10 秒才停应用不响应 SIGTERM加 -t 30,并修优雅停机
重启 daemon 后容器全没了没开 live-restore开 live-restore
日志把磁盘写满没配轮转truncate 清空 + 配 max-size