
1. 容器到底是什么先拆掉认知门槛搞 Docker 的人经常遇到一种尴尬跟同事说“用容器跑一下”对方第一反应是“哦虚拟机吧”。这是最大的误区。容器不是虚拟机它是一个运行在宿主操作系统之上的隔离进程准确说是“被命名空间和 cgroup 约束住的普通进程”。你把它看成一台迷你电脑也行但内核还是宿主的内核没有自己的操作系统这正是它轻量、启动快、占用小的根源。我用一个生活类比讲清楚镜像像烤蛋糕的模具模具决定了成品的样子——里面装什么料、什么形状、多大规模都是模具定义的容器则是用这个模具烤出来的一块块蛋糕同一套模具可以烤无数块每块独立存在、可以各自加不同的奶油和水果互不影响。镜像就是那个只读模具容器就是可写的运行实例你在容器里改任何东西都不会污染模具本身。理解了这个关系后面所有命令都是顺理成章的docker run是用模具做一块新蛋糕docker ps是看现在有哪几块蛋糕在桌上docker stop是把某块蛋糕放进冰箱docker rm是扔掉某块。这套心智模型一旦建立Docker 学习曲线能平缓一半。再说容器和虚拟机的差别。虚拟机里跑的是完整体统从内核到用户态全套自理所以它占几个 GB 起步容器直接共享宿主内核只隔离用户空间一个精简的 Alpine 镜像才几 MB。虚拟机启动相当于冷启动一台物理机容器启动就是启动一个进程几百毫秒的事。代价是容器不能随便换内核Windows 上跑 Linux 容器必须借助 WSL2 或 Hyper-V 的轻量虚拟机来托底这也是 Docker Desktop 在 Windows 上比在 Linux 上麻烦的原因。容器的核心价值不是省钱是交付一致性。以前“在我机器上能跑到你机器上就跑不了”是团队协作里的日常摩擦现在你直接把镜像打包出去镜像里是编译好的二进制、配置好的环境变量、固定版本的依赖到任何一台装了 Docker 的机器上行为一致。配合 CI/CD从代码提交到镜像构建到容器部署全链路自动化这才是容器化部署项目的真正意义。2. 装好 DockerLinux 和 Windows 两条路线各走各的坑2.1 Ubuntu 安装与验证按官方源走最稳在 Ubuntu 上装 Docker最靠谱的不是apt install docker.io而是走官方 apt 源装 docker-ce。两者区别在于docker.io是发行版帮你打包的版本通常落后官方一两个大版本docker-ce是 Docker 官方维护的社区版新特性、安全修复跟得及时。我平时环境里直接装 ce 版省得后面碰到版本兼容性问题还要返工。安装流程分四步卸载旧包、添加 GPG 密钥、添加软件源、安装并设置开机自启。具体命令如下sudo apt-get remove docker docker-engine docker.io containerd runc sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完先验证再走下一步sudo systemctl status docker docker version docker run hello-worldhello-world能正常输出提示信息说明守护进程和 CLI 都通了。这里我踩过一次坑新装的机器上docker run一直报Cannot connect to the Docker daemon at unix:///var/run/docker.sock.查了一圈就是 docker.service 没起来systemctl start docker之后就正常了。遇到这个报错先看服务状态不要急着卸载重装。把当前用户加进 docker 组可以省掉每次输 sudo 的麻烦sudo usermod -aG docker $USER newgrp docker注意加组之后要重新登录一次终端才生效。而且这个操作只适合自己用的开发机生产环境的机器别图省事把用户塞进 docker 组docker 组权限等同于 root风险很高。2.2 Windows 上 Docker Desktop 的经典报错与 WSL2Windows 装 Docker 走的是 Docker Desktop它本质上是在 Windows 里起一个 Linux 虚拟机内核来跑 Linux 容器。最常见的报错就是那句virtualization support not detected或者Docker Desktop failed to start because virtualization support is not enabled。出现这个报错的原因有几种按概率排BIOS 里没开虚拟化、Windows 功能里的虚拟机平台没启用、Hyper-V 被关了、或者你用的是旧版 Windows 10 而没装 WSL2。排查顺序建议固定第一确认 CPU 虚拟化是否开启。打开任务管理器性能选项卡里看“虚拟化”如果显示“已禁用”那就得重启进 BIOS找 Intel Virtualization Technology或 AMD SVM Mode把它改成 Enabled。笔记本用户注意有些品牌机 BIOS 里默认锁着这个选项不同品牌路径不一样但关键词基本是 Virtualization、VT-x、SVM 这几个。第二启用 Windows 功能。控制面板里打开“启用或关闭 Windows 功能”勾上“适用于 Linux 的 Windows 子系统”和“虚拟机平台”确定后按提示重启。这一步补齐 WSL2 的运行基础。第三装 WSL2 内核。直接在命令行执行wsl --update装完用wsl --status检查默认版本确保是 2。wsl -l -v可以看每个发行版的版本号。这三步走完重启 Docker Desktop绝大多数 virtualization 相关报错都能解决。还有一类坑是 Docker Desktop 启动后一直转圈日志里报failed to connect to the Docker API at npipe:////./pipe/dockerDesktopLinuxEngine——这是 Docker Desktop 的 Linux 引擎没起来常见诱因是 WSL 里跑着太多发行版导致内存被挤爆或者 Docker Desktop 本身版本太老。先执行wsl --shutdown清理所有 WSL 实例再重启 Docker Desktop不行就检查 Docker Desktop 的 Resources 设置把内存配额调大。这招实测下来对大部分“转圈起不来”的场景都有效。Docker Desktop 里另一个高频需求是往已部署的容器里增删文件。Windows 上没有 Linux 那种直接的 shell 通路实践做法有两种一是docker cp在容器和宿主机之间复制文件适合拷单个文件二是装 VS Code 的 Dev Containers 插件直接打开容器内的目录可视化操作文件体验和本地编辑一样。我自己倾向后者因为查看日志和改配置都在一个窗口里完成效率高很多。2.3 镜像加速配置不配置后面会卡到怀疑人生拉镜像慢几乎是每个新人的第一道坎。Docker 默认从 Docker Hub 拉取国内直连的速度和稳定性都不理想。解决思路是配置镜像加速器修改/etc/docker/daemon.json{ registry-mirrors: [https://docker.m.daocloud.io] }改完执行sudo systemctl daemon-reload sudo systemctl restart docker才生效。Windows 上的 Docker Desktop 在 Settings - Docker Engine 里改同一个 JSON。平时代码里大家还经常碰到docker pull直接超时的情况除了配加速器还有个很多人不知道的点尽量拉带具体版本号的镜像不要一律latest。latest标签对应的层级缓存命中率低而且版本漂移会在几周后给你带来“明明没改代码行为却变了”的灵异事件。养成锁定版本的习惯收益是长期的。3. 镜像管理你每天跟容器打交道的底层操作3.1 拉取、查看、删除三大基础命令的细节镜像操作是容器使用的地基。先说拉取docker pull nginx:alpinenginx是镜像名alpine是标签。不写标签默认拉latest。alpine 标签的意义在于镜像极小nginx 官方版百来 MBalpine 版才几十 MB因为底层换用了精简 musl libc 的 Alpine Linux。对性能和体积有要求的场景优先选 alpine 变体。查看本地镜像用docker images输出里有个 IMAGE ID 字段删除时可以用它也可以用仓库名:标签。删除命令docker rmi nginx:alpine如果镜像被运行中的容器引用会删除失败。这时要么先停容器再删要么用docker rmi -f强制删。-f会连引用的容器一起清理平时慎用线上更别用。用不上的悬空镜像none标签的中间层会一直占磁盘定期清理命令docker image prune -a这个命令会删掉所有未被容器引用的镜像包括中间层。清之前心里有点数下次再用得重新拉但对长期跑 CI 的机器来说隔几个月清一次能腾出几十 GB。还有docker system df可以查看镜像、容器、数据卷各占多少空间排查磁盘告警时第一件事就是跑它。3.2 镜像安全别什么镜像都敢往生产里拉热词里有“镜像安全和容器安全”这个值得单独讲。镜像安全的核心问题有三个来源不可信、内容有漏洞、版本带后门。来源不可信是最大的坑。Docker Hub 上大量镜像不是官方发布的有些是个人上传的里面可能塞了挖矿程序、反弹 shell 甚至键盘记录器。我一个朋友从 Hub 上拉了个号称“全家桶优化”的镜像跑起来之后机器 CPU 跑满查了半天发现镜像里带挖矿脚本。所以拉镜像之前养成一个习惯第一看是不是官方源比如nginx、redis、mysql这种不带前缀的就是官方维护第二看下载量第三看镜像的层数是否合理。内容有漏洞的问题可以借助扫描工具。Docker Desktop 内置了镜像扫描能力命令行可以用docker scout或第三方工具 Trivy。扫描结果会列出 CVE 漏洞和严重性等级对暴露到公网的镜像至少把 critical 级别的漏洞处理掉再上线。拉取之后还要学会验证签名。Docker 官方镜像支持内容信任Docker Content TrustDCT开启后只接受已签名的镜像export DOCKER_CONTENT_TRUST1开启后拉取未签名的镜像会直接失败。这在发布流水线里非常有用相当于给“从仓库到镜像”加了一道防篡改的锁。日常开发可以不开生产构建必须开。最小化原则也是安全的一部分。一个镜像里装的东西越少攻击面越小。构建 Dockerfile 时用FROM alpine而不是FROM ubuntu用--no-install-recommends装依赖、装完清理缓存这些做法在减小体积的同时也顺手砍掉了安全隐患。我曾经把一个 Spring Boot 业务镜像从 900MB 减到 150MB就是把基础镜像从带完整 JDK 的大镜像换成带精简 JRE 的小镜像启动速度和部署时间都明显改善。4. 容器生命周期从运行到排查的完整实操4.1 启动容器run 和 create 的区别要分清docker run是create start的组合也是最常用的一条命令。项目里跑一个 Nginx 容器docker run -d --name web -p 8080:80 -v /opt/html:/usr/share/nginx/html:ro nginx:alpine各参数含义-d后台运行、--name给容器起名、-p 8080:80把宿主机的 8080 映射到容器的 80、-v /opt/html:/usr/share/nginx/html:ro把宿主机目录挂载进容器并设为只读。-p的宿主端口选好就不要轻易改因为你可能已经有一堆调用方依赖它。docker create则只是创建容器但不启动适合先创建再单独配置网络的场景。大部分情况下直接用 run 就够了但理解 create 的存在有助于你理解“容器 可写层 配置”的本质。启动容器后要验证是否真的起来了第一反应是docker ps docker logs --tail 100 webdocker ps看状态STATUS 是 Up 说明活着。然后看日志确认服务进程有没有就绪。Nginx 这类容器很快MySQL、Java 这类慢启动容器可能要等十几秒日志里出现ready for connections或Started才算真正起来。4.2 停止、重启、删除状态切换的完整图谱容器的状态机看似简单但新手很容易在“停止后还能不能 exec”这件事上绕晕。直接给结论docker stop停容器容器还在磁盘上可以docker start重新拉起。docker restart等于 stop start优先级比 stop 高一点实际是给容器内的 PID 1 发 SIGTERM超过默认 10 秒再发 SIGKILL。docker rm删除容器删了之后容器号和可写层都没了数据如果没挂载卷就会一起丢。docker rm -f强制删除运行中的容器。停止和删除之间是有犹豫空间的。线上环境误删容器后如果有卷挂载数据还在重跑一个就行如果没挂卷数据直接蒸发。所以我强烈建议所有有状态的服务必须挂数据卷这条后面细讲。批量操作场景开发机上几十个容器要一起停手动一个个敲太蠢用docker stop $(docker ps -q)$(docker ps -q)返回所有容器 ID再传给 stop。同理可以批量删docker rm $(docker ps -aq)带-a是因为要包含已停止的容器。这套组合拳在处理测试环境大扫除时特别好用。4.3 进入容器与文件拷贝运维动作的日常要进容器里排查问题最常用的是docker exec -it web /bin/sh-i保持标准输入打开-t分配一个伪终端。注意alpine 镜像里没有/bin/bash只有/bin/shUbuntu 系镜像才有 bash。进去之后能跑 ps、看 /etc 下的配置、手动执行进程命令但容器里通常没有编辑器改文件要么用宿主机改完拷进去要么从容器拷出来改完再放回去。文件拷录用docker cp /opt/local.conf web:/etc/nginx/conf.d/default.conf docker cp web:/var/log/nginx/access.log ./access.log第一个是宿主机文件推进容器第二个是容器文件拉出来。docker cp支持在容器和宿主机之间双向复制不需要容器里装任何工具这点非常方便。一个容易忽略的操作查看容器内进程。docker top web直接显示容器里的进程列表和宿主机的top类似能快速确认进程有没有因为权限、依赖问题挂掉。配合docker stats看实时 CPU/内存占用排查性能问题时就靠这两个命令。4.4 容器秒退问题aborted(core dumped) 的前因后果热词里有一条“启动 python 容器自动退出显示 aborted(core dumped)”这是容器新手最容易碰到的场景之一。容器本质是跑一个前台进程一旦这个进程退出容器就跟着退出。docker ps看不到它要用docker ps -a或docker logs查原因。Python 容器秒退通常有四个原因入口命令写错比如 CMD 里写的是交互式命令没有前台进程、依赖缺失import 报 ModuleNotFoundError、权限问题容器内用户对挂载目录无写权限、或者核心代码抛异常直接崩了。排查顺序是docker logs 容器名日志里如果是ModuleNotFoundError说明镜像里缺依赖Dockerfile 里要加RUN pip install xxx如果是段错误segfault或者 aborted先怀疑基础镜像和 Python 版本的兼容性换官方 python 镜像的重试一下。还有个很隐蔽的坑容器里跑 uvicorn 或 gunicorn 时忘加--host 0.0.0.0服务只监听 127.0.0.1容器外面访问不到但容器自身不报错行为表现为“好像没起来”。这个我踩过不止一次建议看到日志正常但连不上端口时第一反应就去看监听地址。5. 场景化部署实操MySQL 8.0 与 Redis 主从5.1 用容器跑 MySQL 8.0版本、参数、数据卷一个都不能少MySQL 容器化部署是热词里的常客我直接给出一个拿过来就能用的方案docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPass123 \ -e MYSQL_DATABASEappdb \ -e MYSQL_USERappuser \ -e MYSQL_PASSWORDappPass123 \ -v /opt/mysql/data:/var/lib/mysql \ mysql:8.0这里几个关键点环境变量不能省。MYSQL_ROOT_PASSWORD必须有否则容器启动失败MYSQL_DATABASE会自动创建一个空库MYSQL_USER和MYSQL_PASSWORD创建业务账号。业务账号只给业务库的最小权限root 密码只留在宿主机配置里别在代码里出现。数据卷必须挂。/var/lib/mysql是 MySQL 的数据目录不挂卷的话容器一删数据全没。挂到宿主机/opt/mysql/data之后删容器重跑还能找回数据。这条是 MySQL 容器化的底线没有商量余地。字符集和时区建议显式设置。MySQL 8 默认字符集已经是 utf8mb4但时区默认 UTC和业务机器差 8 小时写代码时容易出各种时间错乱。启动后执行SET GLOBAL time_zone 08:00; SET time_zone 08:00;或者在启动参数里加--default-time-zone08:00。生产环境更推荐把时区配置在 MySQL 参数文件里而不是靠容器环境变量因为环境变量不是所有场景都能生效。连接验证宿主机直接mysql -h127.0.0.1 -P3306 -uappuser -p能连上说明端口映射和账号都没问题。连不上先查三件事容器状态是不是 Up、宿主机端口有没有被占用、防火墙放没放行。5.2 Redis 主从复制两条命令搭建的最小集群Redis 容器化的主从搭建比想象中简单因为 redis 镜像本身就提供了--replicaof参数。最小主从架构只需要两个容器docker run -d --name redis-master -p 6379:6379 redis:7 redis-server --appendonly yes docker run -d --name redis-replica -p 6380:6379 redis:7 redis-server --slaveof 172.17.0.2 6379注意第二行的172.17.0.2是 master 容器的 IP怎么拿执行docker inspect redis-master | grep IPAddress或者docker network inspect bridge都能看到。在容器化场景里容器间的通讯建议走 Docker 内部网络而不是宿主机 IP因为 172.17.0.x 网段在内网环境下更稳定端口映射反而增加复杂度。验证主从是否同步在 replica 容器里执行docker exec -it redis-replica redis-cli info replication看master_link_status:up就说明链路通了。我遇到过master_link_status:down的情况排查后发现是 master 的bind 127.0.0.1配置导致只允许本机访问replica 自然连不上。解决办法是启动参数里去掉默认 bind 或用--bind 0.0.0.0。这种问题日志里通常有-DENIED Redis is running in protected mode的提示看到它就知道是 bind 或 protected-mode 挡了路。真正生产环境一般不会手动搭而是用 docker compose 一步到位下面说。5.3 Docker Compose用 5 条命令编排一套完整应用热词里有一条“如何用 3 个容器和 5 条命令快速完成 hermes webui 多容器部署”讲的就是 Compose 的价值。Compose 的核心是把多个容器的启动参数写进一个 YAML 文件docker compose up -d一条命令全部拉起。一个典型的多容器应用前端 后端 Redis MySQL的 compose 文件长这样version: 3.8 services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: appdb volumes: - db_data:/var/lib/mysql networks: - appnet cache: image: redis:7 networks: - appnet backend: build: ./backend ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: prod depends_on: - db - cache networks: - appnet frontend: image: nginx:alpine ports: - 80:80 volumes: - ./frontend/dist:/usr/share/nginx/html:ro depends_on: - backend networks: - appnet volumes: db_data: networks: appnet: driver: bridge几个要点depends_on只是控制启动顺序不保证依赖服务已经可用。比如 backend 容器启动时如果 MySQL 还在初始化连接就会失败。处理方式是给 backend 加 healthcheck或者代码里做重试。我一般用 healthcheck 方案db: healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s timeout: 3s retries: 10然后 backend 里depends_on加条件depends_on: db: condition: service_healthy这算是 Compose 进阶用法但生产环境几乎是刚需不然你就得接受“第一次启动时 backend 崩溃重启一次才正常”的体验。Compose 常用命令就那 5 条up -d启动、ps看状态、logs -f看日志、down -v停止并清理-v会连数据卷一起删慎用、exec进容器。真正日常维护用这五条就覆盖了九成场景。6. 网络与数据持久化容器化绕不开的两座山6.1 端口映射和三种网络模式容器的网络模型经常把人绕晕我用一个图景说明默认的 bridge 网络相当于给每个容器发了一个内网 IP172.17.x.x容器之间能互相访问但宿主机外面访问不到。要让外面能访问必须做端口映射-p 8080:80把宿主机 8080 端口的流量导入容器的 80 端口。三种网络模式对比模式网络行为适用场景bridge默认容器有独立 IP通过端口映射对外大多数单机部署host容器直接用宿主机网络无独立 IP追求极致网络性能none无网络离线计算、安全沙箱host 模式最容易被误解它不走端口映射容器里的进程直接监听宿主机的端口。比如容器里 Redis 监听 6379宿主机上直接就能访问 127.0.0.1:6379。好处是延迟低坏处是端口冲突和网络隔离都没了一个容器占用的端口另一个容器不能再用。容器间通讯尽量别用宿主机 IP 加映射端口的方式直接走容器名。bridge 网络下 Compose 自动做了 DNS 解析backend 里连接数据库写jdbc:mysql://db:3306/appdb就能通不需要知道 db 容器具体 IP。这是很多人搞了半天网络不通的根源用了宿主机 IP 或 localhost而容器内 localhost 指的是容器自己。6.2 数据卷bind mount 和 volume 怎么选容器内文件系统是临时的容器删除后可写层直接销毁。要持久化数据必须挂卷。两种方式bind mount 是直接挂宿主机目录比如-v /opt/mysql/data:/var/lib/mysql。好处是宿主直接能看到文件备份、迁移都方便坏处是受宿主机目录权限影响SELinux 环境经常需要额外处理。volume 是 Docker 管理的存储比如-v db_data:/var/lib/mysql数据存在/var/lib/docker/volumes/下。好处是 Docker 统一管理备份用docker run --rm -v db_data:/data -v /backup:/backup alpine tar czf /backup/db.tar.gz /data这种套路安全又简单。我的选择标准是开发环境、需要经常看文件用 bind mount生产环境、数据安全优先用 volume。MySQL、Redis 这类有状态数据库用 volume 更稳因为 Docker 对 volume 有更好的权限和备份支持bind mount 在某些情况下会因为宿主目录权限问题导致容器内写不进去。6.3 网络不通的排查顺序“容器网络不通”是热词里的高频问题排查思路要固定成一条链路第一步确认容器是否真的运行中docker ps。很多“网络不通”本质是容器压根没起来端口没监听。第二步确认端口映射docker port 容器名。它会列出容器的端口映射关系如果这里没有输出说明-p参数没生效或容器使用 host 模式。第三步宿主机测连通性curl http://127.0.0.1:8080。通说明问题在防火墙或外部网络不通进容器看进程监听。第四步进容器看监听地址docker exec -it 容器名 sh然后netstat -tlnp或ss -tlnp。看到0.0.0.0:80没问题看到127.0.0.1:80就是程序本身只监听了回环地址。第五步检查防火墙。Ubuntu 上ufw statusCentOS 上firewall-cmd --list-all。容器端口映射后宿主机防火墙放行宿主机端口即可。这套链路 90% 的网络问题都能定位。剩下的 10% 是 Docker 网络本身的 DNS 或网卡问题重启 docker 服务大概率能解决sudo systemctl restart docker。注意重启 docker 会把所有容器停掉再拉起生产环境谨慎操作。7. 常见问题速查与实战避坑清单7.1 如何确定当前环境是不是 Docker 容器这个需求在排查问题时经常出现你在一个机器上操作不确定自己是直接跑在宿主机上还是已经进了容器。判断方法有三个从最简单到最准确最直接的是看根目录下的.dockerenv文件ls /.dockerenv存在这个文件基本可以确定在容器内。它的存在是 Docker 运行时约定俗成的标记绝大多数容器镜像都有。第二招看/proc/1/cgroupcat /proc/1/cgroup如果里面包含docker或kubepods字样说明进程被容器托管。在 Kubernetes 环境里还会看到kubepods路径。第三招看主机名。容器的主机名默认是容器 ID 的前 12 位一堆随机 hex 字符。hostname输出的如果是一长串类似abc123def456的东西大概率在容器里。这个判断在排查“为什么我改了配置文件不生效”时特别有用。比如你以为是宿主机上改了 Nginx 配置结果自己其实在容器里改的是容器内的临时层重启容器就没了。7.2 Java 容器内存占用高的排查套路热词里“Java docker 容器占用内存特别高怎么排查”是个实战问题。Java 应用在容器里内存高的原因比普通应用复杂因为有 JVM 这个内存管家横在中间。第一坑JVM 没感知容器限制。旧版本 JVM8u131 之前默认按宿主机物理内存来算堆大小容器限了 512MBJVM 却按宿主机 32GB 的一半去申请堆结果 Direct Memory 或堆外内存直接让容器被 OOMKilled。解决办法是显式设置 JVM 参数docker run -d --memory512m -p 8080:8080 \ -e JAVA_OPTS-Xms256m -Xmx256m -XX:MaxMetaspaceSize128m -XX:UseContainerSupport \ my-java-app-XX:UseContainerSupport在现代 JDK 里默认开启但既然用了容器建议显式写死-Xmx不要依赖 JVM 自动探测。第二坑堆外内存和线程栈不计入-Xmx。Metaspace、线程栈、DirectByteBuffer、JNI 内存全都不受-Xmx约束。一个看似只用了 200MB 堆的应用实际 RSS 可能翻倍。排查时用docker stats看容器整体内存再用jcmd pid VM.native_memory看 JVM 内部明细确认堆和堆外的比例。第三坑堆内存泄漏。docker stats显示内存只增不减GC 后堆仍然很高基本是泄漏。先看 GC 日志容器内jstat -gcutil pid 1000如果老年代持续增长且 Full GC 频繁抓堆快照分析。容器环境抓堆快照用docker exec里跑jmap -dump:formatb,file/tmp/dump.hprof pid再拷到宿主机用 MAT 分析。我实践里的经验是排查内存问题优先级是“先看限制参数 → 再看 GC 日志 → 最后看堆快照”不要一上来就 dump 堆容易把容器直接卡死。7.3 窗口容器与 Linux 容器的边界场景WSL 里的 Docker 容器和 Windows 交互时偶尔会碰权限相关的奇怪报错比如热词里那条“应用程序-特定权限设置并未向在应用程序容器中运行的地址”之类的报错。这类问题的根因通常是 Windows 侧的 ACL 权限或 Docker Desktop 的挂载目录权限没配对。处理方向把工作目录从 Windows 文件系统移进 WSL 文件系统\\wsl$\...路径下因为 Docker Desktop 对 WSL 内部文件系统的性能远好于跨文件系统桥接挂载目录给容器用户加写权限或者直接把宿主目录 chmod 777临时方案生产别这么干。这个问题的完整解决要结合具体报错信息大概率是权限问题而不是 Docker 本身的问题。7.4 青龙等脚本框架容器化的常见坑热词里出现“docker 青龙依赖管理”“青龙容器公益版在线安装”这类脚本容器在社区里很流行。青龙本身是个定时任务面板容器化部署的好处是隔离脚本环境。但依赖管理很容易踩坑容器里默认的 Python 环境和宿主机完全隔离你装的依赖重启容器就没了。正确做法是把依赖装进容器镜像而不是运行时去 pip install。要么用 Dockerfile 在构建阶段安装要么把依赖清单挂进去容器启动时自动执行安装脚本。还有一类问题是容器时区不对导致定时任务时间错乱启动时加-e TZAsia/Shanghai或者挂/etc/localtime解决。8. 一些亲测好用的日常习惯最后分享几个我在实际使用中沉淀下来的习惯不复杂但都很顶用。第一个习惯是给所有容器加--restart策略。开发机可以加--restart unless-stopped服务器重启后容器自动拉起省去每次手动启动的麻烦。但加这个策略不能掩盖“容器崩溃后立刻回弹导致日志刷屏”的问题--restart配合 healthcheck 用实测最稳的是生产环境用restart: always加 healthcheck开发环境用unless-stopped。第二个习惯是日志先看再判断。容器出问题我从来不看监控面板直接docker logs --tail 200 --timestamps时间戳能帮你定位崩溃时间点和业务波动是否吻合。启动失败找“ERROR”“panic”“FATAL”连接异常找“refused”“timeout”先排除环境问题再怀疑代码问题。第三个习惯是镜像构建产物打标签不要只写latest用时间戳commit号当标签比如myapp:20250618-3f2a1b。这样回滚时能精准指到上一个稳定版本而latest会在不知不觉中漂移到新版本出问题后你想回都找不到旧版本。第四个习惯是定期清理。开发机上跑一个月镜像、容器、卷乱七八糟占几十 GB 很正常。定时跑docker system prune -af清掉没用的镜像和悬空层用docker system df监控趋势。注意prune -af会把所有未使用的镜像删掉下一次拉取会重新下载配合缓存策略用就行。容器这个东西你用一个月会觉得没什么了不起但真让你回到没有容器的部署流程你会立刻觉得难受到不行。这篇写到的命令和排查套路都是我反复用过、踩过坑才沉淀下来的按这个思路去折腾能把“容器化部署”落地得比大多数团队更稳。遇到具体问题就按章节里的排查顺序走大部分都能在十分钟内定位到根因。