ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Docker常用命令实战指南:从环境配置到微服务部署全解析

Docker常用命令实战指南:从环境配置到微服务部署全解析 到部署这个东西其实大家心里都清楚真正拦住你的往往不是业务代码怎么写而是那一堆绕不开的Docker命令。见过太多人Docker Desktop装完就卡在第一步或者镜像拉下来不会启动再或者容器跑起来之后日志看不到、数据存不住来来回回就是那几个命令记不牢。这篇指南不搞虚的从环境准备一路讲到微服务部署把常用命令按真实使用场景拆开揉碎给你看哪个参数干什么用的、为什么这么写、踩坑怎么排全都有照着一路敲下来基本就够用了。这篇文章适合的人很明确刚接触Docker想快速上手的新手用Docker部署中间件但老是翻车的运维开发以及写微服务项目需要本地联调的同学。我默认你至少会用终端敲命令Linux和Windows的差异我会单独标出来。1. 环境准备Docker装好后面才谈得上命令1.1 Linux 和 Windows 下的安装要点绝大多数云服务器和开发机都是Linux安装方式其实比你想象的简单。Ubuntu/Debian系直接一顿操作sudo apt update sudo apt install -y docker.io sudo systemctl enable --now docker如果是CentOS/RHEL系用dnf或者yum也一样sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl start docker这里有个很有意思的细节很多人装完就跑docker run然后被权限错误卡住。因为Docker守护进程默认只接受root用户的连接普通用户想执行docker命令必须在docker组里sudo usermod -aG docker $USER newgrp docker这行命令的含义是把当前用户加入docker组之后不用每次sudo了。但是注意加了组之后要重新登录一次会话才完全生效有些老手也会栽在这以为装失败了其实只是权限没刷新。Windows上面的坑就更多了。Docker Desktop虽然是个图形界面但它背后依然依赖Windows的虚拟化能力。启动那种“virtualization support not detected”或者“Docker Desktop failed to start”的报错说来说去就是两件事要么BIOS里没开启虚拟化VT-x/AMD-V要么Hyper-V和Windows沙盒没打开。这两个开启的路径不同BIOS开启虚拟化要重启进Setup界面不同主板按键不一样常见的是F2、Del、F10。Windows功能面板控制面板-程序-启用或关闭Windows功能里勾选Hyper-V和“适用于Linux的Windows子系统”。还有一个隐蔽但常见的问题就是电脑上装了别的虚拟化软件比如安卓模拟器会抢占虚拟化资源导致Docker Desktop起不来。这时候把模拟器关掉或者卸载比去改一堆配置都管用。1.2 配置镜像加速不配真的会拉到怀疑人生这一步看起来不是命令但实操中几乎绕不开。直接拉官方镜像在某些网络环境下会慢到怀疑人生。配置方式是在Docker的守护进程配置文件里加registry-mirrorssudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://你的加速地址] } EOF sudo systemctl daemon-reload sudo systemctl restart dockerWindows上Docker Desktop同样支持在Settings - Docker Engine里改同一个JSON结构改完应用并重启就能生效。选加速地址的时候挑一个稳定、不常变的同时也要明白加速器本质上只是个中转如果目标镜像本身就很冷门加速效果也有限。这里解释一下daemon.json的作用所有Docker守护进程的全局配置都收在这里不止是镜像加速像数据目录位置、日志大小限制、网络段设置这些都可以在这里控制。后面排查性能问题的时候还会回到这个文件。1.3 新版Docker的包管理器containerd命令要认识一下containerd这个词这两年跟着Kubernetes火起来的。简单说Docker的容器运行时底层就是调用containerd来管理容器生命周期的只是Docker给它包了一层更友好的命令行工具。不过containerd自己也提供了一套CLI就是ctr命令。ctr images pull docker.io/library/nginx:latest ctr images list ctr tasks list ctr containers start 容器ID实际操作中日常运维Docker容器的还是用docker命令但如果你用的是纯Kubernetes环境里面没有docker CLI只有containerd那么ctr和crictlKubernetes专用CRI工具就成了救命稻草。特别是排查节点状态时会用到crictl ps、crictl logs来替代docker ps、docker logs。我的建议是CTR命令不用熟但至少知道它存在、能干什么遇到纯CRI环境时不至于完全懵。2. 镜像管理下载、查看、删除与构建2.1 拉取镜像的常用姿势镜像就是容器的模板拉镜像这个动作对应docker pull。最常见的用法是docker pull nginx docker pull mysql:8.0 docker pull redis:7.2-alpine第一个命令不带标签默认拉latest标签第二个指定了大版本号8.0第三个不仅指定了版本还指定了Alpine变体。Alpine是什么它是一个极小化的Linux发行版体积通常只有标准镜像的一半甚至三分之一。拿Redis举例redis:7.2标准版镜像是130MB左右而redis:7.2-alpine只有40MB上下启动速度和资源占用明显更优。但两个点一定注意Alpine镜像基于musl libc个别软件会存在兼容性问题如果是生产环境跑成熟的业务优先用官方标准版体积大点换来的是更少的怪问题。镜像标签是会发生变化的latest标签指向的镜像今天和明天可能不是同一个这在写部署脚本、做版本回滚的时候是隐患。真实项目里最好固定到具体的小版本比如mysql:8.0.33。拉镜像之前还可以先看看本地已有的镜像避免重复拉取docker images docker image ls -a说句实话docker images和docker image ls功能基本一致区别就在于后者是更完整的命令结构便于统一理解和写脚本。本地镜像列表里有几列信息很重要最左边是仓库名之后是标签、镜像ID、创建时间和大小。每次操作前看一眼本地镜像能省去很多不必要的重复下载。2.2 删除镜像为什么有时删不掉docker rmi nginx docker rmi 镜像ID docker image prunedocker rmi按名称或ID删镜像docker image prune把悬挂镜像即没有标签、也没有容器使用的镜像批量清理掉。实操中发现删不了的情况基本都是一个原因有容器还在用它。哪怕这个容器处于退出状态只要容器实体还在镜像就不能删。解决办法很简单先找到并删掉对应的容器docker ps -a docker rm 容器ID docker rmi 镜像ID还有一个空间杀手是构建缓存。每次Dockerfile里改了某一行重新构建之前生成的层缓存并不会自动清理。你看着磁盘明明没存什么大文件却快满了多半是构建缓存撑起来了。用这个命令查看占用docker system df它会列出镜像、容器、数据卷、构建缓存的磁盘占用。如果构建缓存那项数字很高直接清docker builder prune -f2.3 构建镜像Dockerfile 不是玄学镜像不全是拉来的自己写的服务要打包成镜像靠的是Dockerfile和docker build命令。一个最小可运行的Dockerfile长这样FROM openjdk:17-jdk-slim WORKDIR /app COPY target/my-service.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建命令docker build -t my-service:1.0.0 .-t指定镜像名称和标签最后的.表示构建上下文是当前目录。这里有个高级技巧值得说.dockerignore文件作用类似.gitignore把target、node_modules、.git这些大目录排除在外不然它们会被一并发送到守护进程导致构建超慢。层这个概念是理解Dockerfile的关键。每一条指令都会生成一个只读层层之间有缓存如果某条指令没变后续构建会直接复用之前的层所以依赖安装的指令应该尽量写在前面、放代码的COPY写在后面这样代码改一行依赖层缓存还能命中构建速度快很多。2.4 镜像导出与加载离线环境救命有时候内网服务器不能直接拉取镜像解决办法就是在外网机器上把镜像保存成文件拷进内网再加载docker save -o nginx.tar nginx:latest docker load -i nginx.tardocker save和docker export看起来都能导出东西但完全是两码事。save是保存整套镜像包含多个层和元数据之后能用来直接创建容器而export是把运行中的容器文件系统打成一个tar包里面没有镜像层信息不能重新构建出带完整历史的镜像。实际运维中常用的是save/loadexport/import一般只在迁移单个容器文件系统时才有用。3. 容器生命周期从创建、启动到销毁3.1 docker run 是整个Docker命令里的高频之王docker run不是单做一个动作而是“创建启动”一步到位。新手最需要先背下来的一条docker run -d --name my-nginx -p 8080:80 nginx:latest拆解一下-d后台运行英文detach不加这个参数你细看终端会发现它被日志卡住CtrlC之后容器也停了。--name给容器起个名字后续操作都靠这个标识不指定的话Docker会随机生成一个看起来像某个科学家名字的ID。-p 8080:80端口映射宿主机8080端口转发到容器内80端口。冒号左边是宿主机端口右边是容器内端口这个顺序写错就会导致访问不到服务。最后的nginx:latest是镜像名称Docker会先看本地有没有没有就自动去仓库拉。跑起来之后用浏览器打开http://localhost:8080看到Nginx欢迎页说明这一套流程通了。实际的经验是容器中很多服务默认监听的端口并不一定是常规的80/3306比如MySQL默认3306、Redis默认6379、Tomcat默认8080。映射的时候右边一定要写成容器内真实监听端口左边才是你对外暴露的端口。左右搞反了镜像怎么改参数都访问不通。3.2 容器查看命令别用肉眼猜状态容器启动之后第一件事就是查状态docker ps docker ps -adocker ps只显示运行中的容器-a把已经退出的容器也列出来。列出来的状态字段STATUS里会显示“Up 2 hours”“Exited (0) 3 minutes ago”之类的内容后面数字是退出码。退出码0说明是正常结束非0说明运行中出了异常。查这个状态是排查问题的第一步。如果想知道容器里到底是什么情况就要看日志docker logs my-nginx docker logs -f --tail 100 my-nginx-f是持续跟踪输出类似tail -f配合--tail 100只显示最后100行这个组合在排查启动失败时能快速定位到错误原因。日志默认是标准输出和标准错误也就是你在容器里执行程序时打印到终端的内容。没有输出基本可以断定程序卡在启动阶段了。查容器的更多细节信息也有专门命令docker inspect my-nginx docker inspect --format {{.NetworkSettings.IPAddress}} my-nginxinspect会输出一大段JSON包含网络、挂载、环境变量、Entrypoint等所有信息排查时很好用。格式化输出那段是取容器IP地址的常用方式虽然现在容器间通信更多走服务名而不是IP但知道怎么取值总没坏处。3.3 进入容器与执行命令docker exec -it 必会容器不像虚拟机它里面往往没有SSH服务所以想进容器操作用的是docker execdocker exec -it my-nginx /bin/bash-i保持标准输入打开interactive的意思。-t分配一个伪终端有了它才能看到命令提示符和彩色输出。/bin/bash在容器内执行的命令。两个参数缺一个都会有区别缺了-t命令能执行但看不到交互界面缺了-i输不了指令。所以它们通常是一起用写成-it。如果容器是最精简的Alpine镜像里面没有bash那就用/bin/shdocker exec -it alpine-container /bin/sh还有一种更隐蔽的情况容器里连sh都没有比如纯二进制镜像这时候你只能通过docker exec去做操作了比如直接运行程序查看帮助docker exec my-app /app/myapp --help另一个用法很实际就是把宿主机上的配置文件拷贝进容器docker cp ./my.conf my-nginx:/etc/nginx/conf.d/ docker cp my-nginx:/etc/nginx/nginx.conf ./backup/这里要注意容器本质上是一个临时环境任何在容器内直接改的文件在容器被删除后都会消失所以重要的配置和数据一定用数据卷持久化这个后面单独讲。3.4 停止、重启与删除别急着把容器全清掉docker stop my-nginx docker start my-nginx docker restart my-nginxstop是优雅停止会先给容器发送SIGTERM信号给它一点时间处理善后如果过段时间还不停才会发SIGKILL强制杀掉。start是启动一个已存在的容器restart是重启。这几个命令理解起来不复杂但有个细节常见于生产事故docker stop是有默认超时时间的默认10秒如果业务本身需要更长时间做优雅下线可以用--time参数延长比如docker stop -t 60 my-nginx。删除容器的命令要小心docker rm my-nginx docker rm -f my-nginx docker container prune不带参数时docker rm只能删除处于停止状态的容器-f会先强制停止再删除。container prune会把所有已停止的容器一次性清掉。这里必须提醒容器一旦删除它内部的可写层数据就没了如果没做持久化等于数据直接报废。所以执行rm之前务必确认数据卷的挂载情况。4. 网络与数据持久化让容器能服务、不丢数据4.1 端口映射的完整参数掌握好前文提到-p 8080:80这只是端口映射的短格式。完整写法是docker run -d -p 0.0.0.0:8080:80/tcp --name nginx nginx:latest意思是在宿主机所有IP的8080端口上监听TCP流量转发到容器的80端口。之所以默认写成-p 8080:80也能用是因为Docker会默认绑定0.0.0.0。如果不希望宿主机上其他网卡也暴露这个端口可以精确指定绑定IPdocker run -d -p 127.0.0.1:8080:80 --name nginx nginx:latest这样只有本机能通过localhost访问外部网络访问不到本地调试时安全性更好。生产环境一般还会在宿主机前面挂一层负载均衡器或网关Docker的端口映射只是其中之一。如果你用的是端口全随机模式docker run -d -P --name nginx nginx:latest docker port nginx大写-P会把容器内所有暴露的端口随机映射到宿主机高位端口docker port能查到具体的映射关系。这种模式在自动化和多实例部署时见过但平时用得少。4.2 Docker网络模式bridge、host与自定义网络Docker的默认网络模式是bridge容器会通过一个虚拟网桥docker0与宿主机通信每个容器有自己的独立IP。它的好处是隔离性好但容器间互相访问就要走IP或服务名。如果直接用--network host启动docker run -d --network host --name nginx nginx:latest容器直接共享宿主机网络栈不需要端口映射直接用localhost访问容器服务。这种方式性能好、没有NAT开销但容器的端口直接暴露在宿主机上多个容器如果监听同一端口会冲突所以一般用于对网络性能要求极高的场景。多个容器需要互相通信时更好的做法是创建一个自定义网络让容器通过服务名互通docker network create my-net docker run -d --network my-net --name mysql mysql:8.0 docker run -d --network my-net --name app my-app在同一个自定义网络里app容器里可以直接用mysql:3306访问数据库服务解析出来就是mysql容器的IP不需要去查IP。这个特性在开发环境中特别方便因为容器重启后IP会变但服务名始终不变。4.3 数据卷容器删了数据还在容器是短暂的但业务数据需要长期保存所以就有了数据卷。常见的挂载方式有几种docker run -d -v /my/custom:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD123456 --name mysql mysql:8.0 docker run -d -v mysql-data:/var/lib/mysql mysql:8.0第一种是bind mount把宿主机目录/my/custom挂载到容器内/etc/mysql/conf.d宿主机上的文件改动会即时反映到容器内适合放配置文件、日志备份。第二种是命名卷Docker在宿主机上自动管理一块目录通过卷名mysql-data引用数据不会因为容器删除而丢失。生产环境里数据库的数据要放这种命名卷里否则rm容器的那一瞬间数据库数据就跟着灰飞烟灭了。查看和管理数据卷docker volume ls docker volume inspect mysql-data docker volume pruneinspect能看到这个卷在宿主机上的真实路径这个路径在备份数据时有用。prune会把不再被任何容器引用的卷清掉磁盘紧张的机器定期执行能释放不少空间但一定要先确认没有用这个卷恢复数据的计划。4.4 资源限制不加限制容器能吃掉整台机器容器虽然看上去像独立进程但它毕竟共享宿主机内核。一个服务出现内存泄漏不限制的话可能把整台服务器拖垮。所以生产环境一定要加资源限制docker run -d --memory512m --cpus1.5 --name app my-app--memory限制最大内存超出会被内核杀死OOM Killed。--cpus限制CPU。还有更细的--memory-swap、--pids-limit这些高级参数日常用前两个就足够。排查内存超限导致的容器退出光看docker logs是看不到杀掉原因的需要docker inspect app | grep -i oom docker events --filter containerappdocker events能实时输出事件日志如果看到OOMKilled事件说明内存限制设小了要么扩容要么优化程序内存使用。5. Docker Compose 实战一条命令拉起整套服务5.1 为什么需要 Compose手动执行docker run一条条敲管理两三个容器还能忍一旦涉及MySQL、Redis、后端服务、前端静态站点、消息队列这几个组件混在一起手动命令就乱成一锅粥了。Compose的价值在于把多个容器的启动配置写进一个YAML文件然后一条命令全部启动。Compose文件的基础结构version: 3.8 services: web: image: nginx:latest ports: - 80:80 app: build: . environment: - SPRING_PROFILES_ACTIVEprod depends_on: - mysql mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:里面每个service对应一个容器通过depends_on控制启动顺序通过environment传环境变量通过volumes做持久化。启动和停止docker compose up -d docker compose ps docker compose logs -f docker compose downup -d是启动全套服务down会删除所有关联容器但不会删命名卷里的数据这点比docker rm安全得多。如果连卷一起清理加-v参数这个命令非常危险执行前一定要确认。5.2 用Compose部署MySQL 8.0从零到可用部署MySQL是Compose最常见的实战场景。一个带初始化和数据持久化的完整配置如下services: mysql: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: root123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql - ./mysql-init:/docker-entrypoint-initdb.d - ./my.cnf:/etc/mysql/conf.d/my.cnf command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: mysql-data:MYSQL_ROOT_PASSWORD是MySQL 8.0镜像的强制要求不设置是启动不了的。mysql-init目录下的脚本会在首次初始化数据库时自动执行适合放建表语句和初始化数据。my.cnf挂载进去可以把自定义配置带进容器里。命令上指定utf8mb4字符集避免中文乱码。启动之后用MySQL客户端连接mysql -h127.0.0.1 -P3306 -uroot -p在容器内执行SQL命令docker exec -it mysql8 mysql -uroot -p docker exec -it mysql8 mysql -uroot -p -e show databases;-e参数直接在命令行执行SQL语句适合脚本里自动执行初始化任务。这里有一个常见的坑MySQL 8.0默认的认证插件是caching_sha2_password如果你用旧版的客户端连接会报认证失败。解决办法是创建用户时指定mysql_native_passwordCREATE USER app% IDENTIFIED WITH mysql_native_password BY password; GRANT ALL PRIVILEGES ON *.* TO app%; FLUSH PRIVILEGES;5.3 Redis 主从架构Compose一行全解决Redis主从是典型的一主多从架构用Compose写起来很清晰services: redis-master: image: redis:7.2-alpine container_name: redis-master ports: - 6379:6379 command: redis-server --appendonly yes redis-slave: image: redis:7.2-alpine depends_on: - redis-master command: redis-server --slaveof redis-master 6379配置里master开着AOF持久化slave通过--slaveof redis-master 6379自动连接主节点。这里依赖的是Compose内置的DNS解析服务名redis-master会被解析成对应容器IP所以slave不需要知道master的具体IP。启动后验证主从关系docker exec -it redis-master redis-cli info replication输出中role:master和connected_slaves表示主从连接正常。如果slave显示连接失败多数情况是master没有设置bind 0.0.0.0或者防火墙拦了6379端口。因为Redis镜像默认会在容器内监听所有接口实际上宿主机网络也能通所以在Compose文件里给master加上--bind 0.0.0.0更稳妥。6. 微服务项目部署的Docker思路6.1 容器编排Compose 是轻量方案微服务项目往往不是一个服务而是若干个服务的集合。如果只是本地联调或中小型项目Compose完全够用。编排的思路是每个服务独立一个目录里面放Dockerfile。在最外层放一个compose.yaml统一管理所有服务。公共组件MySQL、Redis、Nacos、Kafka用官方镜像业务服务用自己构建的镜像。网络统一走同一个Compose网络服务间通过服务名互访。举个例子一个包含网关、用户服务、订单服务加数据库的项目compose像这样services: gateway: build: ./gateway ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: docker depends_on: - user-service user-service: build: ./user-service environment: DB_HOST: mysql SPRING_PROFILES_ACTIVE: docker depends_on: - mysql order-service: build: ./order-service environment: DB_HOST: mysql SPRING_PROFILES_ACTIVE: docker depends_on: - mysql mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:每个微服务构建时注意调整Dockerfile里的COPY路径因为构建上下文是各自的服务目录不是根目录。启动顺序上depends_on只能保证容器启动顺序不能保证服务内的应用完全就绪所以业务代码里一定要有重试机制不然连接数据库会报连接拒绝。6.2 更新与回滚技巧微服务更新是常事手动rm再run是最笨的办法。稳妥一点的做法是docker compose build user-service docker compose up -d user-service如果更新后发现问题要回滚最快的办法是重新指定旧镜像的标签docker compose up -d --no-deps user-service:1.2.0Compose里的image或者build变了up -d会重新创建容器但数据卷不会动所以数据库数据安全。如果更新过程中出现“container name already in use”的报错多半是旧容器没有清理干净先执行docker compose down再up就行。再高级一点可以用容器的健康检查来控制发布节奏services: user-service: build: ./user-service healthcheck: test: [CMD, curl, -f, http://localhost:8080/actuator/health] interval: 30s timeout: 5s retries: 3健康检查通过后网关转发流量时就不会打到没就绪的服务实例上这在优雅上下线场景里非常实用。7. 常见问题与排查技巧实录7.1 高频报错与解决速查表下面这些报错是实际操作中遇到最多的每一条都有对应的处理方向。报错场景关键信息解决思路Docker Desktop无法启动virtualisation support not detected开启BIOS虚拟化启用Hyper-V或WSL2容器启动后立刻退出Exited (1) 或 Exited (127)用docker logs看应用日志多半是配置错误或依赖没起来权限不足permission denied while trying to connect to the Docker daemon socket把用户加入docker组并重新登录端口占用port is already allocated换宿主机端口或停掉占用端口的进程镜像拉不下来net/http: TLS handshake timeout配置镜像加速检查网络容器内时间不对Container time is UTC环境变量加TZAsia/Shanghai数据库启动失败Cant start server: Bind on unix socket数据卷权限不对检查挂载目录权限Compose无法解析服务名Name or service not known确认服务在同一个网络或者depends_on没配这张表基本覆盖了大多数初学阶段会碰到的坑。遇到报错第一步不是网上搜而是先看日志日志里往往直接告诉你答案。7.2 垃圾清理C盘/系统盘塞满的元凶是构建缓存很多Windows和macOS用户的Docker Desktop越用越卡打开Docker Desktop的磁盘使用统计镜像和缓存占了上百GBC盘直接飘红。清理思路docker system df docker system prune docker system prune -a --volumes单独执行docker system prune会清理停止的容器、悬空镜像、没用的网络和构建缓存是个安全选项。-a会把所有没有被容器使用的镜像也清掉可能会误删你需要回滚的旧版本镜像。--volumes会波及数据卷慎重。如果只想清理构建缓存不碰数据docker builder prune这几个命令在Windows上同样能用因为Docker Desktop内置的CLI和Linux版本的docker命令一致。用之前先docker system df看一眼什么占用最多别一股脑全删。7.3 性能问题排查三板斧容器跑的慢、CPU飙高、内存暴增按这个顺序排查看容器资源占用docker statsdocker stats实时显示每个容器的CPU和内存占用哪匹“黑马”在吃资源一目了然。看进程日志docker inspect --format {{.State.Pid}} 容器ID ps -ef | grep 那个PID查出对应系统进程后能进一步分析它在干什么。不过日常排查有docker top就够了docker top 容器ID看日志是否刷屏docker logs --tail 100 容器ID如果日志疯狂刷错误多半是外部依赖不通引发的重试风暴这类问题光改容器参数没用要修服务之间通信。7.4 给新手的最后几条经验每敲一条命令先搞清楚它改了什么再敲下一条。尤其是rm、prune、down -v这几个危险命令执行前把业务影响想清楚。容器命名别偷懒一个明确的容器名能省去查ID的时间像docker stop mysql8比docker stop d3f2a1c舒服太多。生产环境不要用latest标签每次部署都锁定具体版本出问题能精准回滚。定时清理无用的镜像和构建缓存不然半年之后磁盘会给你一个“惊喜”。多看别人的compose文件很多报错和坑光靠文档是看不出来的别人踩过的坑就是你少走的弯路。我在实际使用Docker的过程中最大的体会是命令本身不难难的是理解每条命令背后的机制和边界。你知道了容器是短暂的、数据要靠卷持久化、网络靠服务和端口串联很多报错就自然能推断出原因了。希望这篇指南能让你从“看到Docker命令就头疼”变成“闭着眼也能把服务跑起来”。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进