
拿Docker当日常主力工具久了你会发现真正影响效率的往往不是那些花哨的高级功能而是最基础的命令有没有用对、参数有没有记牢。工作中见过不少同事明明是一样的操作有人能在几秒内完成镜像构建和容器编排有人却在日志里翻半天、在容器删不掉、磁盘被占满这些破事上反复折腾。这篇文章是一次系统性的沉淀把Docker日常使用中最高频、最关键的常用命令梳理成一份可直接对照的速查手册同时把背后涉及的最佳实践、参数取舍、踩坑经验一并整理出来。不管你是刚接触容器的开发新手还是已经用了一段时间但全靠复制粘贴的中间选手正常看完可以直接照着操作省去踩坑时间。这份内容不是简单搬运官方文档而是把我在本地开发、测试环境、生产排障中实际用过的命令和配置做了过滤保留真正高频有用的部分去掉那些用得极少又容易误导人的选项。每个命令都会说明为什么这么用、什么时候该换另一种写法、哪些地方容易出问题这样你记住的不只是一串命令而是命令背后的判断标准。1. 镜像管理一切容器操作的起点镜像管理是Docker使用频率最高的模块。不管是拉取现成镜像、构建自定义镜像还是清理本地残留镜像每天都在反复操作。这一部分先把最常用的镜像命令拆开讲清楚再把实际使用中最容易踩的坑直接列出来。1.1 拉取与查看镜像的正确姿势拉取镜像是所有容器操作的第一步基础命令是docker pull但很多人只记住了最简单形式忽略了一些实用参数。docker pull nginx:latest docker pull mysql:8.0 docker pull -q alpine:3.18 docker pull --platform linux/amd64 ubuntu:22.04-q参数可以静默拉取只显示镜像ID适合脚本里使用。--platform参数在跨平台场景下非常有用比如你在Apple Silicon芯片的Mac上运行Docker Desktop有些旧镜像没有arm64版本这时候可以显式指定拉取amd64版本配合模拟运行虽然性能会有损失但至少能跑起来。反过来在x86服务器上想临时调试arm镜像也可以用这个参数。查看本地镜像的命令是docker images同样有几个常用变体docker images docker images -a docker images --digests docker images --filter danglingtrue-a会显示所有层级的镜像注意它和普通列表的区别。--filter danglingtrue会列出所有的悬空镜像也就是那些没有标签、只占用空间的历史镜像层这些通常是在反复构建过程中产生的后面清理时会用到。这里有一个实操中很容易误判的点docker images只显示顶层镜像不显示共同依赖的中间层。你拉取的镜像看起来只有几个实际磁盘占用却很大就是因为中间层、基础层都在共享。真正要看磁盘占用要用docker system df后面会细讲。1.2 构建镜像不能忽略的两个细节构建镜像是从项目代码到可运行容器的核心步骤命令本身很简单但有两个细节往往决定构建成败和镜像质量。docker build -t myapp:v1.0 . docker build -f docker/Dockerfile -t myapp:v1.0 . docker build --no-cache -t myapp:v1.0 . docker build --pull -t myapp:v1.0 .第一是构建上下文的概念。很多人以为docker build里的.只是指定Dockerfile位置其实它指的是整个构建上下文目录Docker引擎会把整个目录打包上传到守护进程再在隔离环境中执行构建。如果你的项目目录里有node_modules、.git、target这类体积巨大的文件每次构建都会把这些文件一并上传拖慢速度不说还可能因为上下文过大直接超时。解决办法是在项目根目录编写.dockerignore文件和.gitignore类似把不需要的目录和文件排除掉。第二是缓存机制下的层顺序优化。Dockerfile里的每一条指令都会生成一个层如果某一条指令的文件没有变化Docker会复用之前的缓存层。利用这个特性应该把不常变化的指令放在前面频繁变化的放在后面。比如一个Node.js项目正确顺序是先复制package.json和package-lock.json执行npm install再复制源码。因为依赖文件不常变每次改代码重新构建时依赖安装那一步会命中缓存构建速度能快不少。如果反过来先复制全部源码再安装依赖每次代码变动都会导致依赖重装构建时间成倍增加。# 推荐写法先装依赖再复制源码 FROM node:20-alpine WORKDIR /app COPY package.json package-lock.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . CMD [node, server.js]1.3 镜像清理的权限问题与判断标准镜像积累到一定程度磁盘空间告急清理成了刚需。但清理要谨慎docker rmi不是想删就能删容器正在使用的镜像会直接拒绝删除。docker rmi myapp:v1.0 docker rmi -f myapp:v1.0 docker image prune docker image prune -adocker image prune只删除悬空镜像也就是没有标签、不被任何容器引用的镜像这个相对安全。-a参数会把所有未被容器使用的镜像全部干掉包括你只是拉下来备用的镜像空间释放效果明显但下次要用还得重新拉取需要权衡。实际操作中我一般先执行docker ps -a看看有没有要保留的容器再执行docker image ls --digests核对镜像版本最后才动手清理。曾经在测试环境执行docker image prune -a结果把本地唯一一份Oracle镜像给删了重新拉取花了大半天那次之后养成习惯清理之前先确认引用关系。2. 容器生命周期从创建到销毁的完整闭环容器生命周期管理是Docker操作的核心包括创建、启动、停止、重启、进入、删除等环节。这一部分命令直接决定你日常操作顺不顺手。2.1 一条docker run命令的关键参数拆解docker run是创建并启动容器的命令参数非常多但实际高频使用的就那几个。记住一个核心逻辑docker run相当于docker create加docker start容器创建和启动可以分开执行。docker run -d --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e TZAsia/Shanghai \ -v mysql_data:/var/lib/mysql \ --restart always \ mysql:8.0逐个说关键参数-d表示后台运行不加的话容器会在前台运行日志直接输出到终端CtrlC 会停止容器。--name给容器命名不加会自动生成一个随机的长名字不方便管理。-p端口映射格式是宿主机端口:容器端口。注意左侧是宿主机端口右侧是容器内服务的端口很容易写反。-e设置环境变量镜像不同需要的环境变量也不同具体要查镜像的官方文档。-v挂载数据卷持久化数据防止容器删除后数据丢失。--restart always设置重启策略容器退出后自动重启生产环境基本都会加。这里有个容易忽略的细节docker run命令的镜像名后面是可以追加启动命令的用于覆盖镜像默认的启动命令。比如docker run -d --name redis-dev redis:7.0 redis-server --appendonly yes这样可以在不修改镜像的情况下直接覆盖CMD或追加启动参数很实用。如果你有多个容器端口相互冲突比如本地已经有一个MySQL占用3306再启动一个就会失败报错信息会直接提示端口被占用。这时候最简单的方法就是改宿主机端口映射-p 3307:3306容器内部端口不变外部用3307访问。2.2 进入容器与文件拷贝的两种常用路径容器跑起来之后需要进入容器内部查看日志、执行命令、排查问题。两种常用方式适用场景不同。第一种是docker exec在运行中的容器里执行命令docker exec -it mysql-dev bash docker exec -it mysql-dev mysql -uroot -p docker exec -u root -it myapp sh-it是-i和-t的组合-i保持标准输入打开-t分配一个伪终端。简单理解就是让你像在真实终端里操作一样和容器交互。不加-t的话某些交互式程序比如vi、mysql客户端会显示错乱。docker exec只是进入容器执行命令不会改变容器本身的运行状态。容器的基础命令集不完整可能没有vim、ps、top这些工具这时候优先用系统自带的命令排查或者临时安装。但需注意在容器里安装的软件容器删除后就没了不会保留到镜像里。第二种是docker attach直接附着到容器主进程的标准输入输出上docker attach mysql-devattach会把你的终端直接接到容器的主进程上如果这个进程是交互式的你就能直接操作更像是进入了容器。但它有个坑从attach模式退出时如果按CtrlC很可能会把容器主进程一起终止直接把容器停掉。我的建议是除非确实需要附着到主进程否则日常操作一律用docker exec更安全也更灵活。文件拷贝在调试和部署中也很常用docker cp ./local-file.txt myapp:/app/ docker cp myapp:/var/log/app.log ./logs/不想进入容器就能直接拷贝文件非常方便。注意docker cp也支持从宿主机拷贝到容器内未运行的容器前提是容器已经创建了这个特性在临时恢复文件时很有用。2.3 停止与删除容器时经常被忽略的超时参数停止和删除容器看起来简单实际上有不小的坑。默认情况下docker stop会给容器发送SIGTERM信号等待默认10秒后如果容器还没退出就会发送SIGKILL强制杀死。问题就在这10秒上某些应用启动时加载了大量数据或建立了许多连接10秒根本不够优雅退出。docker stop -t 60 mysql-dev docker restart -t 30 myapp docker kill myapp-t参数可以调整宽限时间对于数据库、消息队列这类有状态应用建议设置更长的时间比如30到60秒让应用有足够时间保存数据、断开连接、执行清理逻辑。docker kill是直接发送SIGKILL相当于强制杀死应尽量避免除非应用完全无响应。删除容器的命令和删除镜像不一样要区分开docker rm mysql-dev docker rm -f mysql-dev docker rm -v mysql-devdocker rm只能删除已经停止的容器运行中的容器会报错。-f是强制删除会自动停掉运行中的容器再删除。-v会同时删除匿名数据卷避免残留无用数据卷占用空间。批量删除是日常运维的常用技巧docker rm $(docker ps -aq) docker stop $(docker ps -q) docker container prunedocker ps -aq列出所有容器ID$(...)命令替换后批量传给docker rm。生产环境执行这条命令要极度谨慎建议先用docker ps确认一下列表再执行或者加过滤条件。3. 网络与存储卷容器通信和数据持久化的基础传统开发中应用连数据库直接连IP加端口就行但容器化之后网络和数据存储的逻辑发生了根本变化。容器IP不固定、容器删除数据即失这些新问题都必须在网络和存储卷层面解决。3.1 端口映射与三种自定义网络模式端口映射解决的是宿主机外部如何访问容器内服务的问题前面在run命令里已经提到。这里重点说网络模式的选择因为不同网络模式决定了容器之间是否可达、主机名如何解析。Docker默认有几种网络模式bridge默认、host、none。bridge模式下容器通过虚拟网桥连接宿主机相互之间可以通信但不能直接通过主机名访问。host模式下容器直接共享宿主机的网络栈没有独立IP端口直接监听在宿主机上。none模式下容器没有网络连接只能在里面跑一些完全不需要网络的程序。日常使用中需要手动创建bridge网络来管理容器通信docker network create --driver bridge --subnet 172.20.0.0/16 my-net docker network ls docker network inspect my-net docker network connect my-net myapp docker network disconnect my-net myapp创建自定义bridge网络最大的好处是可以进行自动DNS解析容器之间能直接通过容器名访问不需要维护固定的IP地址。这在微服务架构里非常关键服务之间不需要关心对方的IP只要知道服务名就行。命令行写IPC地址容易记混有个很实用的查看技巧不用进入容器直接用inspect命令docker inspect -f {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} mysql-dev不过还是建议优先使用容器名通信而不是依赖IP。因为容器重启后IP可能变化而容器名一般不变。3.2 数据卷挂载的两种写法和区别容器是临时性的删除再重建就什么都没了。数据持久化的核心手段是挂载存储卷也就是把宿主机的目录或Docker管理的卷挂载到容器内部路径。第一种是命名卷docker volume create mysql_data docker run -d -v mysql_data:/var/lib/mysql mysql:8.0命名卷由Docker管理数据存储在Docker的数据目录下优点是迁移方便可以使用docker volume系列子命令操作。缺点是位置不直观不好直接去宿主机查看文件内容。第二种是绑定挂载直接把宿主机的目录挂载进去docker run -d -v /opt/mysql-data:/var/lib/mysql mysql:8.0 docker run -d --mount typebind,source/opt/mysql-data,target/var/lib/mysql mysql:8.0绑定挂载的路径是你自己指定的查看、备份都很直观适合开发环境直接修改配置文件或代码热更新。缺点是需要自己管理指定的目录如果忘记备份那目录被误删就全没了。实际操作中有一个坑挂载目录的权限问题。容器内进程以特定用户运行比如MySQL的mysql用户如果宿主机的挂载目录权限不对容器启动时会报权限错误。解决办法是手动调整目录权限比如chown -R 1000:1000 /opt/mysql-data具体UID要看镜像内部使用的用户。很多时候报错信息里会明确提示Failed to change ownership of directory看到这个优先排查挂载目录权限。3.3 容器间通信用别名还是IP容器间通信是实际场景中最高频的网络操作。常见误区是去查IP、写IP但这个方案很脆弱。更好的方式是利用Docker自带的DNS解析能力。在同一个自定义bridge网络中容器可以直接通过容器名或--network-alias指定的别名访问对方docker network create app-net docker run -d --name mysql-dev --network app-net --network-alias db mysql:8.0 docker run -d --name app-server --network app-net myapp:v1.0在app-server容器里连接数据库时直接写db:3306就能访问到mysql-dev容器。这里的关键是必须先创建网络然后两个容器都接到同一个网络上。如果容器不在同一个网络里即使知道了IP也ping不通。和使用固定IP相比用容器名通信的好处非常明显容器重建后IP可能改变但名称不变应用配置不需要调整。微服务部署时服务间的调用地址通常都写服务名加端口由容器编排工具统一管理DNS解析这是容器化后的标准做法。如果容器启动时忘记加入指定网络不用重建容器直接执行docker network connect就能动态加进去这个操作不需要重启容器非常方便。4. 日志、资源排查与系统清理日常运维中日志查看和资源排查占据了相当大比例的排障时间。Docker提供的日志查看命令看着简单实际上参数组合用好了能节省大量排查时间。同时容器长时间运行之后系统空间被占满也是常见的疑难杂症。4.1 日志查看时最实用的组合参数docker logs是最常用的命令但很多人只用了最简单的docker logs 容器名把所有日志从头到尾刷一遍效率很低。docker logs -f mysql-dev docker logs --tail 200 mysql-dev docker logs --since 2025-01-01T00:00:00 mysql-dev docker logs -t myapp-f是follow模式持续输出新日志和tail -f效果类似排查问题时要实时观察日志变化时用这个。--tail只显示最后N行适合快速浏览最近的日志。排查崩溃问题先执行docker logs --tail 100 容器名看看最近发生了什么。--since按时间过滤日志支持相对时间和绝对时间格式比如--since 30s、--since 30m。当机器半夜崩溃时这个参数非常有用直接看凌晨几点那段时间的日志。-t加上时间戳方便和业务日志记录的时间做对齐。日志不显示的问题也很常见。有些容器日志不是输出到标准输出而是直接写到文件。这时候docker logs是看不到内容的需要进入容器查看日志文件或者调整应用的日志配置把日志同时输出到标准输出。还有一种情况是日志驱动配置成json-file但容器已经运行很久日志量巨大执行docker logs会卡顿这时候先清理日志文件或者调整日志轮转策略。4.2 资源占用异常时的定位命令容器运行缓慢、宿主机负载异常时第一件事是判断是哪个容器在消耗资源。docker stats可以在终端实时显示所有运行中容器的CPU、内存、网络、磁盘IO使用情况。docker stats docker stats --no-stream docker stats mysql-dev--no-stream只输出一次当前状态不持续刷新适合在脚本中获取快照数据。单看docker stats能发现问题但要进一步定位进程级别的资源占用需要进入容器内执行top或ps。有时容器内的top所显示的CPU使用率和宿主机没有直接对应关系因为容器共享宿主机的内核top看到的进程实际上是宿主机进程的一部分。这时候更准确的方式是使用docker inspect查看容器的资源限制配置确认没有残留异常进程。4.3 空间被容器占满后的清理步骤磁盘空间不足几乎是每个Docker使用者都遇到过的问题。镜像、容器、网络、数据卷、构建缓存都会占空间盲目删除不可取先看清空间分布再对症下药。docker system df docker system df -v docker system prune docker system prune -a --volumesdocker system df会显示镜像、容器、本地卷、build cache各自占用的空间总量。发现哪一块占用异常再做针对性清理。-v可以查看更详细的层级信息比如哪个数据卷占用最多、哪个容器关联了哪些数据卷。docker system prune是综合清理命令会清理停止的容器、悬空镜像、未使用的网络、build cache。-a会额外清理所有未被使用的镜像--volumes会把无主的数据卷也一并删除。--volumes要慎用数据卷里可能有重要的数据库数据如果挂在容器上没有事一旦容器被删了数据卷就成无主卷执行--volumes清理时会被直接删掉。实际踩过这样一坑测试环境跑了一套GitLab容器一直正常某天做例行清理时执行了docker system prune -a --volumes结果GitLab所有数据卷被判定为无主卷并被删除整个仓库数据瞬间消失。事后复盘发现当时那个GitLab实例是用docker run启动的没有被compose管理容器重启策略异常导致容器被标记为停止数据卷就被当成了无主卷。所以教训就是数据库等有状态应用的数据卷要么用Compose统一管理要么清理前先备份--volumes参数能不用就不用。5. Docker Compose从单机命令到编排部署当项目的容器数量超过两三个再一条条docker run就太失控了。Compose通过一个YAML文件描述整个服务栈的配置一条命令完成创建和启动这是从临时调试到正式部署的关键过渡。5.1 compose文件里必设的三个字段一个基础但完整的compose文件至少包含三个字段services、networks、volumes。services定义每个容器服务networks声明服务之间通信的网络volumes声明持久化卷。version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql-dev restart: always environment: MYSQL_ROOT_PASSWORD: 123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5 app: build: . depends_on: mysql: condition: service_healthy ports: - 8080:8080 environment: DB_HOST: mysql DB_PORT: 3306 volumes: mysql_data:企业级最佳实践里关键字段包括restart重启策略、healthcheck健康检查、depends_on依赖关系。healthcheck是非常实用的字段它让Compose能够真正感知服务是否就绪而不是只看容器是否启动。比如MySQL容器启动起来了但内部初始化还没完成这时候业务服务就去连接会报连接拒绝。配置了健康检查配合depends_on的条件等待就能解决这个时序问题。避免在compose文件里直接写固定IP服务间通信用服务名即可Compose会自动创建网络并做DNS解析。服务名在compose文件中的定义是唯一的容器之间直接用服务名访问这个特性让容器IP变更对业务无感知。5.2 生产环境常用的几个compose子命令Compose的命令结构是docker compose加子命令基本每天都在用的有这几个docker compose up -d docker compose up -d --build docker compose down docker compose ps docker compose logs -f docker compose exec app bash启动时-d表示后台运行--build会先重新构建镜像再启动代码有变动时用这个。down会停止并删除所有容器和默认网络如果加了-v会把compose文件里声明的所有数据卷一并删除这个参数必须极度谨慎。ps查看服务状态logs查看聚合日志exec进入指定服务容器。docker compose pull在更新镜像版本时很有用它会拉取最新的远程镜像但不会自动重启容器还要配合up -d使用。5.3 配置热加载与更新发布的注意事项改完compose文件后重新执行docker compose up -d就可以了Compose会检测到配置变化并重新创建发生变化的容器。这种更新方式很稳因为Compose只重建配置有变更的服务其他服务不受影响。更新镜像版本时通常流程是先改compose文件里的镜像tag然后执行docker compose pull docker compose up -d这一套流程在正式环境已经验证过很多次稳定可靠。唯一需要注意的问题是如果直接改了服务名或者容器名Compose会把它当成新服务处理旧服务不会被删除时间久了会出现一正一旧多个容器同时存在的情况。该清理的旧容器要及时清理。配置变更回滚也是实际高频需求。只要之前的配置还在把compose文件改回旧版本再执行docker compose up -d就能回滚镜像用的是本地旧镜像拉取不到会fallback到已有镜像但建议提前确认本地还存在期望的旧镜像不然回滚时会因为没有对应镜像而失败。6. 镜像瘦身与构建优化的落地手段镜像体积直接决定拉取速度和使用成本尤其在带宽受限或镜像仓库存储空间有限的情况下精益的镜像构建意味着显著的成本节约。这一部分把镜像瘦身和构建优化结合起来讲它们是同一件事的两面。6.1 多阶段构建解决什么痛点多阶段构建是镜像瘦身的最有效手段。核心思想是在一个临时的构建阶段里安装编译工具、下载依赖编译出最终产物之后把产物复制到一个全新的、精简的基础镜像里整个过程只保留最后阶段的产物和运行环境。典型的JaveSpring Boot、Node.js、Go项目都能用这个模式。以Node.js为例# 构建阶段 FROM node:20-alpine AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --registryhttps://registry.npmmirror.com COPY . . RUN npm run build # 运行阶段 FROM nginx:alpine COPY --frombuild /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80构建阶段有完整的Node环境和源码最终运行阶段只有Nginx和静态文件体积从几百MB降到几十MB。如果不需要Nginx只用nginx:alpine这样的基础镜像都没有必要直接用alpine加一个静态文件服务器模块也很轻量。没有多阶段构建的老式写法是装一堆构建工具把整个工具链留在镜像里生产环境根本用不到编译工具纯属浪费。多阶段构建不只是体积优化对安全性也有提升攻击面变小了因为运行阶段的基础镜像里没有额外的编译工具和源码。6.2 .dockerignore与构建上下文的关系前面提到构建上下文的概念.dockerignore是控制上下文体积的标配手段。如果没有这个文件项目目录里的一切文件都在构建时被打包上传包括node_modules、dist、logs、.git这些根本不需要进入镜像的内容。node_modules .git .gitignore dist logs *.log Dockerfile docker-compose*.yml README.md.dockerignore的语法和.gitignore高度相似支持通配符。写完这个文件之后构建速度会有质的提升因为上下文体积从几百MB变成几十KB上传和解析时间大幅缩短。还需要注意.dockerignore只影响构建上下文不影响构建过程中的 RUN 指令。也就是说你没法通过它在镜像里排除某个文件那些文件只要在构建上下文中且Dockerfile里用了COPY指令还是会进入镜像。所以镜像最终体积的优化主要靠Dockerfile里的指令控制和多阶段构建.dockerignore解决的是构建上传效率和指令误拷贝问题。6.3 精简基础镜像在日常项目里的选择基础镜像的选择直接决定最终镜像体积的下限。常见选择有这么几类debian/ubuntu功能完整包管理强大但体积大一般几百MB。slim后缀版本官方精简版去掉了不必要的文档和包体积大约是完整版的三分之一。alpine基于Alpine Linux体积极小一个基础镜像可能只有几MB到十几MB但兼容性有一些坑。distroless只包含运行库没有包管理器、没有shell安全性高但维护和排障难度大。日常项目推荐优先用slim版本兼顾体积和易用性遇到问题还能进容器装工具排查。alpine虽然体积最小但很多原生编译的库在里面需要额外处理可能碰上musl libc兼容性问题偶尔会出现某些二进制运行不了的情况。曾经在alpine镜像里跑一个编译好的Java程序因为缺少glibc直接报错后来换回eclipse-temurin:17-jre-jammy才解决。用-alpine的时候一定要先验证应用能否正常运行再决定是否正式采用。如果只是图体积小结果应用起不来那是捡了芝麻丢了西瓜。7. 常见问题排查与避坑实录使用Docker的过程中几乎每个人都会遇到几类典型的故障。这一部分把最常遇到的几个问题整理成速查表同时分享排查思路不求一次覆盖所有场景但求在你遇到问题时有章可循。7.1 Windows环境下Docker Desktop启动失败Windows上装Docker Desktop最常见的问题是启动时报错提示虚拟化支持未检测到或者WSL 2相关错误。这类问题几乎都和系统虚拟化、WSL 2的配置有关。排查路径是自下而上的先确认BIOS/UEFI里虚拟化开关有没有打开也就是Intel VT-x或AMD-V再确认Windows功能里的适用于Linux的Windows子系统和虚拟机平台是否启用最后看WSL 2是否正常运行。很多时候启用功能后需要重启重启后还是报错再检查Docker Desktop的WSL 2 backend设置。也有用户机器不支持嵌套虚拟化比如一些虚拟机软件里再跑Docker Desktop虚拟机需要开启硬件虚拟化透传才行。同一类问题还有一种解法改用WSL 1 back-end性能有损失但某些老机器可行优先还是先解决虚拟化支持问题。如果按这条路径排查仍然失败常见原因是旧版本Docker Desktop和当前Windows版本不兼容卸载重装最新版本即可。Windows下Docker的使用体验整体不如Linux顺手但靠这套排查路径能解决80%的启动问题。7.2 容器时区不正确导致日志时间对不上容器默认时区是UTC比北京时间晚8个小时。表现在日志上就是容器内打印的时间和实际业务时间对不上排查客户反馈的系统时间和日志时间相差8小时的问题。解决方式通常有三种第一种是在docker run时设置环境变量docker run -d -e TZAsia/Shanghai your-image这种方式依赖镜像内部支持读取TZ环境变量很多官方镜像如MySQL、Redis支持但部分应用镜像并不支持设置了也无效。第二种是挂载宿主机的时区文件docker run -d -v /etc/localtime:/etc/localtime:ro your-image适用于Debian/Ubuntu系镜像。但Alpine Linux基础镜像使用musl libc对/etc/localtime的解析方式不同可能不起作用。这时候需要在Dockerfile里安装时区数据或者在构建时把时区文件复制进去。第三种是直接改造Dockerfile安装时区数据FROM alpine:3.18 RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone踩过这个坑以后我在构建所有业务镜像时都会显式处理时区避免在应用层调试8小时偏差。启动阶段多花一步后面排查省大量时间。7.3 端口映射正常但连接超时的排查路径容器已经启动日志显示服务正常运行端口映射也配置了但外部就是连不上。这个问题看着诡异实际排查路径很固定。第一层确认端口映射正确性docker ps看PORTS列确认宿主机端口和容器端口映射没写反。常见错误是-p 8080:80写成-p 80:8080结果外网80端口访问一个不存在的服务。第二层确认防火墙规则宿主机防火墙可能阻止了外部对该端口的访问。Linux下检查firewalld или ufw规则云服务器还要看安全组是否放行了对应端口。第三层确认容器内部服务监听在正确地址上。有些服务默认监听127.0.0.1只接受本机回环访问容器外访问当然连不上。需要把服务监听地址改为0.0.0.0或容器IP。第四层确认网络模式。如果容器用了host网络模式端口就不能单独映射了服务和宿主机共享网络栈访问时直接用宿主机端口即可不需要再做端口映射。把这一条路径完整过一遍90%的端口连接问题能解决。剩下10%可能是应用层面的超时比如MySQL的max_connections打满、连接被拒绝这些要结合业务日志去查了。7.4 容器日志无限增长导致磁盘写满容器日志是磁盘空间被占满的重要元凶。Docker默认的日志驱动是json-file默认情况下日志文件没有大小限制也不自动轮转长时间运行的容器会无限占用磁盘空间。有两种应对方式都建议在启动容器时或compose配置里就做好第一种是全局配置修改Docker守护进程配置在/etc/docker/daemon.json里加入{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样每个容器的单个日志文件最大10MB保留最近3个文件超过就轮转清理。配置修改后需要重启Docker守护进程才会生效新创建的容器会应用该配置已有容器不会自动生效。第二种是针对某个容器的配置docker run -d --log-opt max-size10m --log-opt max-file3 your-image这种方式更灵活适合单个高频日志服务。两个方案结合使用日志占满磁盘的坑基本能避开。遇到日志已经写满磁盘的情况不要直接删日志目录因为文件被进程占用删了空间也不一定释放。正确做法是通过日志轮转或重启容器让Docker重新处理。如果日志文件对应的容器还在运行直接删除宿主机上的日志文件文件句柄还持有空间不释放需要重启容器或执行truncate操作清空文件内容才真正释放空间。8. 镜像仓库与跨主机迁移镜像的生命周期不止于构建和运行镜像仓库的登录推送、镜像的导出导入、跨主机迁移都是实际工作中高频场景而且往往在关键时刻才意识到平时没练熟。8.1 登录、推送与拉取私有镜像的完整流程开发环境偶尔会用私有镜像仓库生产环境则基本都依赖内部仓库。操作流程是固定的docker login registry.example.com docker tag myapp:v1.0 registry.example.com/myapp:v1.0 docker push registry.example.com/myapp:v1.0 docker pull registry.example.com/myapp:v1.0docker login要输入仓库地址、用户名和密码。登录凭证默认保存在~/.docker/config.json里如果临时登录的机器不需要保存凭证加--password-stdin从标准输入读取密码避免在shell历史里留下痕迹更稳妥的方式是把密码写入CI/CD平台的Secret里由流水线注入。给镜像打tag时registry.example.com/myapp:v1.0的命名规则是仓库地址加项目名加标签如果只有myapp:v1.0Docker默认会认为是Docker Hub上的官方镜像。私有仓库的地址一定要写全不然push时会被拒绝。退出登录是docker logout registry.example.com如果有多台机器共享~/.docker/config.json要记得清理不用的凭证。8.2 导出导入镜像与离线部署有些生产环境完全是内网隔离的无法直接访问镜像仓库这时候镜像导出导入就成了唯一的部署途径。docker save -o myapp-v1.0.tar myapp:v1.0 docker load -i myapp-v1.0.tardocker save可以同时保存多个镜像docker save -o all-images.tar img1:v1 img2:v2。注意save保存的是镜像的全部层适合迁移镜像本身。另一种是export导出的是容器的文件系统不包含镜像的层信息和标签历史。docker export -o mycontainer.tar mycontainer docker import -i mycontainer.tar myapp:importedexport和import常用于快速备份运行中容器的当前文件系统状态但不能完整保留镜像的构建历史和标签不推荐作为镜像迁移的标准方式。镜像迁移优先save/load容器快照备份才用export/import。内网部署时没有公网可以拉取基础镜像所以docker pull阶段就要预先准备所有需要的镜像用save打包再带到内网load。建议在打包清单里写上镜像名称和版本长期维护能省去大量沟通成本。9. 常用命令速查总表把高频命令集中整理在一张表里方便哪天忘记参数时快速查找。这张表涵盖了镜像、容器、网络、卷、Compose、系统清理等核心场景。场景命令说明拉取镜像docker pull nginx:latest从仓库拉取镜像查看镜像docker images列出本地镜像删除镜像docker rmi nginx:latest删除指定镜像构建镜像docker build -t myapp:v1.0 .在当前目录构建镜像查看容器docker ps -a列出所有容器启动容器docker run -d --name web nginx:latest后台运行容器进入容器docker exec -it web bash在运行中容器执行命令停止容器docker stop web优雅停止容器强制停止docker kill web直接杀死容器进程删除容器docker rm web删除已停止容器容器日志docker logs -f web实时查看容器日志资源监控docker stats查看容器资源占用拷贝文件docker cp web:/etc/nginx/nginx.conf ./容器和宿主机拷贝文件创建网络docker network create my-net创建bridge网络连接网络docker network connect my-net web将容器接入网络创建卷docker volume create my-vol创建命名卷查看卷docker volume ls列出所有数据卷清理悬空镜像docker image prune删除无标签镜像清理全部未用资源docker system prune -a --volumes慎用会删无主卷Compose启动docker compose up -d启动整个服务栈Compose停止docker compose down停止并删除容器Compose日志docker compose logs -f查看聚合日志镜像导出docker save -o app.tar app:v1.0保存镜像为tar文件镜像导入docker load -i app.tar从tar文件加载镜像查看配置docker inspect web查看容器详细信息查看底层信息docker inspect -f {{.State.Pid}} web用Go模板提取字段快速查阅时这张表加前面各章节的详细说明基本能覆盖日常工作中90%以上的需求。如果你发现自己经常在某个命令上停留太久说明你还没有把最常用的几个参数形成肌肉记忆建议把这些命令手敲几遍直到不再需要翻文档。10. 踩坑记录与避坑心得这一部分是我在做Docker日常运维和开发支持时反复踩过的坑每条都有实实在在的教训。写在这里希望你不用再花同样的时间填坑。10.1 不要在运行中的容器里随便安装软件进入容器后发现缺少工具就顺手apt install或apk add这是新手最容易犯的错误。容器是临时性的在原容器里安装的软件在容器删除后就会消失下次重建还是空白。更重要的是这种临时修改破坏了镜像的不可变特性你无法保证生产环境容器里有哪些软件因为镜像里根本不存在这些安装记录。正确做法是修改Dockerfile增加需要的工具重新构建镜像。如果只是想临时排查问题直接装也无妨但心里要明白这只是临时方案最终要回归到镜像构建流程中去。10.2 端口映射写反是本地开发最高频错误把-p 3306:3306写成-p 3306:3306是不会有问题的问题出在把宿主机端口和容器端口搞反。比如本来想用宿主机3307访问容器3306正确写法是-p 3307:3306如果写成了-p 3306:3307容器启动时会提示端口占用或映射错误因为容器内部3307端口上根本没有服务监听。排查时先看docker ps的PORTS列格式是0.0.0.0:3307-3306/tcp箭头左边是宿主机端口右边是容器端口。看到映射关系再回想一下目标服务的实际监听端口就能快速判断是否写反了。10.3 镜像tag用latest一时爽回溯火葬场镜像tag用latest在本地开发没毛病但上了生产环境就是隐患。你无法通过latest知道具体是哪个版本回滚时更不知道上一次部署的版本是哪一个。某次线上出了个诡异问题排查了半天发现是同事某个时间点push了一个新的latest生产环境在重启时拉到了这个新镜像但所有人都不知道具体变更内容。建议生产环境一律使用语义化版本tag比如v1.2.0、release-20250110。每次发布前更新版本号在镜像名里体现发布信息。本地开发可以继续用latest但要清楚它不是一个稳定的版本标识。10.4 数据卷不能随便清理--volumes慎用前面已经说过--volumes的教训。这里再补充一点如果数据卷没有挂载到任何容器不代表这个数据卷没用它可能是上一次容器删除时遗留的但还有恢复价值的数据。做数据卷清理之前先用docker volume ls查看无主卷列表用docker volume inspect看卷的挂载点确认没有需要的数据再动手。10.5 资源限制一定要设置容器与虚拟机最大的区别之一是资源隔离是软性的。如果把宿主机当作一个多租户环境某个容器内存飙升到OOM会拖垮其他所有容器。所以生产环境就算不设其他限制也要至少设置内存限制和CPU限制docker run -d --name myapp --memory512m --cpus0.5 myapp:v1.0在compose里对应的是mem_limit和cpus用起来更直观。设置资源限制之后单容器问题不再演化成整个宿主机的雪崩至少在故障排查时能快速定位是哪个容器的问题。设置内存限制后容器进程在超过内存上限时会被OOM killer杀掉这是保护机制生效的正常现象。发现容器频繁被OOM杀掉优先调大内存限制或者优化应用本身的内存占用不要一味调高上限。个人经验是在日常工作中真正决定效率的不是会多少个高级命令而是把基础命令用熟了、把常见的坑提前避开。这份速查表是我自己反复查阅、反复补充的版本整理成文后也希望帮到你。也是我这些年最常用的做法先把命令清单放在手边遇到问题随手查查得多了自然记住记住之后再去理解背后的原理比一开始就抱着官方文档啃要高效得多。你可以把上面的命令按自己的习惯精简成一份个人速查表贴在终端或者笔记软件里用几天就会形成肌肉记忆那时候你就能把更多精力放在容器编排、应用架构这些更高维度的事情上了。