首页 / 资讯中心 / 文章详情

Docker指令体系详解:容器、镜像与Compose编排实战指南

Docker指令体系详解:容器、镜像与Compose编排实战指南 ★ FEATURED ARTICLE
废话不多说sir. 直接开写。1. 先把 Docker 指令体系理清楚别一上来就背命令很多新手拿到 Docker 第一反应就是“记命令”什么 docker run、docker ps、docker exec背得滚瓜烂熟真到部署项目的时候照样抓瞎。原因很简单Docker 的“指令”其实是三层结构你只背了第一层的几个高频命令另外两层压根没概念。我理解的 Docker 指令大概分三类第一类是命令行指令也就是你在终端里敲的 docker xxx控制的是容器、镜像、网络、数据卷这四大对象。这是日常用得最多的也是本文的主角。第二类是Dockerfile 指令写在 Dockerfile 文件里像 FROM、RUN、CMD、ENTRYPOINT、COPY 这些。它负责把“镜像怎么构建”这件事描述出来跟命令行指令是两码事。很多人把 docker build 和 Dockerfile 混为一谈其实 docker build 只是执行者Dockerfile 才是真正的剧本。第三类是docker compose 指令严格说是 docker-compose.yml 里的配置指令配合 docker compose up / down 这些命令使用。它的价值在于把多个容器的启动参数从命令行里解放出来用一份 YAML 文件描述整个应用栈。为什么说先理清结构比背命令更重要因为我见过太多人拿着 docker run 的长参数硬撕遇到多容器项目直接崩溃。你把这三层分清楚命令行指令管单机单容器Dockerfile 管镜像构建Compose 管多容器编排脑子里的地图就出来了命令记不牢也没关系查文档就知道该查什么。另外提一个点Docker 指令的大小写是敏感的--name 和 --Name 完全不搭边而且不同操作系统的 CLI 版本可能略有差异比如 Windows 的 PowerShell 和 Linux 的 bash 在某些转义字符上就不一样。做任何跨平台操作之前先 docker version 看一眼版本很多“诡异问题”都是版本差异造成的。2. 容器生命周期指令实操从运行到销毁2.1 docker run启动容器的参数别硬背先搞清楚必选和可选docker run 是最高频的指令没有之一。它的完整语法是docker run [OPTIONS] IMAGE [COMMAND] [ARG...]新手最容易栽的坑是一上来就复制一个几百字的长命令跑通了不知道每段什么意思跑失败了也不知道从哪里排查。我建议把参数拆成三组来理解第一组是必选信息你要跑哪个镜像IMAGE要不要指定版本标签TAG。比如 mysql:8.0没写标签默认就是 latest这个习惯其实不好生产环境一定要写死版本不然哪天你重新拉镜像跑的就是新版本行为全变了。第二组是最常用的运行配置-d # 后台运行不占用当前终端 -p 3306:3306 # 端口映射宿主机端口:容器端口 --name mysql8 # 给容器起名字 -e MYSQL_ROOT_PASSWORD123456 # 环境变量很多镜像依赖它做初始化 -v /data/mysql:/var/lib/mysql # 数据卷挂载把容器目录映射到宿主机第三组是资源限制用过就回不去--memory 512m # 限制内存上限 --cpus 1.5 # 限制CPU核数 --restart always # 容器挂了自动重启生产环境强烈建议拿一个实际部署 MySQL 8.0 的命令举例你就能直观感受到组合方式docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyPass123 \ -e TZAsia/Shanghai \ -v /data/mysql:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ --restart always \ mysql:8.0注意我加了 TZ 时区环境变量还挂载了宿主机的 /etc/localtime。很多容器默认时区是 UTC写完数据发现时间差了 8 小时一脸懵。这种坑其实提前一个参数就可以避开。2.2 容器管理指令ps、start、stop、rm 的配合思路docker run 只是把容器拉起来真正的日常操作其实集中在管理指令上docker ps # 只看运行中的容器 docker ps -a # 看所有容器包括已退出的 docker start 容器名 # 启动已停止的容器 docker stop 容器名 # 优雅停止 docker restart 容器名 # 重启 docker rm 容器名 # 删除容器注意先停止才能删用 docker ps 的时候我有个习惯是加上 -a 也没关系但更建议把列过滤一下docker ps --filter statusexited # 只看已退出的容器这个命令特别实用排查“哪个容器悄悄挂了”一查一个准。删除容器这块有个进阶技巧是连停带删把日志一起清掉别留下残余数据docker rm -f 容器名 # 强制删除运行中的容器 docker system prune # 清理所有停止的容器、没用到的网络、悬空镜像docker system prune 我建议隔一段时间跑一次尤其是本地开发环境能释放好几个 G 的磁盘空间。不过注意这命令会把所有停止的容器全删了如果有想保留的先 docker start 拉起来或者用 docker run 重新创建。2.3 看日志和进容器exec 和 logs 是排障的两只眼睛容器跑起来之后你总得知道里面发生了什么。这时候就需要两个指令docker logs 容器名 # 全量日志 docker logs -f 容器名 # 实时跟踪日志类似于 tail -f docker logs --tail 100 容器名 # 只看最后100行有段时间 MySQL 容器反复启动失败docker logs 里明确写着 data dictionary 初始化失败对应去看挂载目录权限一分钟定位问题。进入容器内部操作用的是docker exec -it 容器名 bash注意这里有个细节有些镜像比较精简比如 alpine 系列的镜像没有 bash只有 sh。你直接 docker exec -it 容器名 bash 会报错 “OCI runtime exec failed”这时候改成 sh 就能进docker exec -it 容器名 sh还有个非交互执行命令的场景比如临时在容器里跑一段 SQLdocker exec -it mysql8 mysql -uroot -p这种写法适合手动操作要是想在脚本里自动化执行可以直接docker exec mysql8 mysql -uroot -p密码 -e show databases;注意这里不加 -it因为脚本环境不需要分配交互终端。3. 镜像管理指令构建与清理镜像才是指令的根基3.1 pull 和 tag拿镜像、打标签版本管理是基本功拉镜像的指令就一句docker pull mysql:8.0但镜像标签这件事值得多说两句。你拉下来的镜像默认叫 mysql:8.0如果你想把它推到自己的私有仓库或者给同一份镜像打多个版本别名就要用 tagdocker tag mysql:8.0 myregistry.com/team/mysql:8.0.1这个指令不会复制镜像只是多了一个引用名字。真正复制出独立镜像的是 docker commit但我不建议日常用 commit 来“保存现场”正确做法是写 Dockerfile 重新构建。清理镜像的指令有两个层级docker rmi 镜像ID或镜像名 # 删除镜像 docker image prune # 清理悬空镜像没打标签的残留层还有我特别喜欢的一键清理docker system prune -a注意这个 -a 会把所有没在用的镜像全删掉本地开发无所谓生产环境千万别先斩后奏先 docker images 看看里面有没有你需要的。3.2 Dockerfile 指令不是每个镜像网络上都现成构建能力必须会你不能总是指望官方镜像满足一切需求。比如你需要在镜像里装一些额外的工具或者在应用启动前做点初始化这时候就要自己写 Dockerfile。Dockerfile 的核心指令就几个FROM ubuntu:20.04 # 基础镜像必须第一行 WORKDIR /app # 设定工作目录 COPY . /app # 把本地文件拷贝进镜像 RUN apt-get update apt-get install -y python3 # 构建时执行的命令 ENV APP_ENVproduction # 环境变量 EXPOSE 8080 # 声明容器暴露端口纯文档性质 CMD [python3, app.py] # 容器启动时执行的命令初学者容易搞混的是 CMD 和 ENTRYPOINT。简单说CMD 是默认启动命令跑 docker run 的时候如果手动指定了命令CMD 会被覆盖。ENTRYPOINT 则是固定入口docker run 后传的参数会追加到 ENTRYPOINT 后面而不是覆盖它。实际构建的时候用docker build -t myapp:1.0.0 .这里的 -t 是给构建出来的镜像打标签最后的 . 是构建上下文目录。我一直强调构建上下文尽量小所以在项目目录里配一个 .dockerignore把 node_modules、.git、dist 这些都排除掉不然每次构建都把一堆无关文件发到 Docker daemon又慢又占空间。3.3 给镜像瘦身构建阶段才体现老手的功力我见过很多同事把镜像折腾到好几个 G原因就是无脑 RUN。优化思路其实不难能用官方镜像就别自己装一堆能用多阶段构建就别留中间产物。多阶段构建是个大招最典型的场景是编译 Go 或 Java 项目# 第一阶段构建 FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o myapp # 第二阶段运行 FROM alpine:latest WORKDIR /app COPY --frombuilder /app/myapp . CMD [./myapp]这里第一阶段的镜像里有完整的编译工具链但运行阶段只需要一个可执行文件于是 COPY --frombuilder 只把编译产物拷贝到精简的 alpine 镜像里。最终镜像体积可以从 1G 缩到几十 M。这个思路放在哪都一样目标是让“运行环境最小可用”。4. 数据卷与网络指令Docker 能生产可用的关键4.1 数据卷容器没了数据还没丢靠的就是 volume容器是“一次性”的rm 之后就什么都没了。要让数据持久化就必须用 volume 或 bind mount。volume 系列指令docker volume create mydata # 创建数据卷 docker volume ls # 查看所有卷 docker volume inspect mydata # 查看卷详情重点是 Mountpoint docker volume rm mydata # 删除数据卷运行时挂载两种方式我之前 MySQL 示例里用的是 bind mount-v /data/mysql:/var/lib/mysql把宿主机的目录直接映射进容器。另一种是命名卷docker run -d --name mysql8 -v mysql_data:/var/lib/mysql mysql:8.0区别在哪命名卷的目录由 Docker 管理你不用管它在宿主机哪个位置备份迁移时也更规范。bind mount 则适合开发环境你能直接在宿主机上看到文件。需要注意的一个坑是容器里运行的用户可能是 www-data 或者 mysqlUID 和你不一样挂载目录的权限不对会导致容器起不来。MySQL 容器常见错误就是 “chown: changing ownership of /var/lib/mysql: Operation not permitted”检查一下宿主机目录的属主和权限chown 到对应 UID 就能解决。4.2 网络指令容器互通与端口映射是另一套学问默认桥接网络下容器之间可以用 IP 通信但容器一重建 IP 就变了。生产环境推荐用自定义网络容器名就是 DNS 名互访不用去查 IP。docker network create mynet docker run -d --network mynet --name app1 myapp:1.0 docker run -d --network mynet --name app2 myapp2:2.0这样在 app2 容器里访问 app1 服务直接 curl http://app1:8080 就能通。应用代码里配置数据库地址也别写 IP写容器名最稳。排查网络问题时我常用这几条docker network ls # 查看所有网络 docker network inspect mynet # 查看网络里的容器和IP docker port 容器名 # 查看端口映射关系遇到“docker 网络不通”的情况优先不是看防火墙而是确认容器是否在同一个自定义网络里。桥接模式下不同容器分属两个网络互相 ping 不通太正常了把他们放进同一个 mynet 里是最直接的解法。5. 部署实战MySQL 8.0 与 Redis 主从反面教材这些指令是每天在用的5.1 MySQL 8.0 部署从指令落地到使用验证热搜里“docker安装mysql8.0并使用”这条基本就是绝大多数后端开发的第一课。我贴一套我这边验证过的完整流程。第一步拉镜像并创建挂载目录docker pull mysql:8.0 mkdir -p /data/mysql/conf /data/mysql/data第二步写一个简单的配置让 MySQL 适应开发环境cat /data/mysql/conf/my.cnf EOF [mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone08:00 EOF第三步启动容器docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRoot123 \ -v /data/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf \ -v /data/mysql/data:/var/lib/mysql \ --restart always \ mysql:8.0这里我把配置文件单独挂载进去比全部覆盖 /etc/mysql 目录更安全因为官方镜像有自己的基础配置你只追加自己关心的配置项不容易冲突。第四步验证使用。等十几秒让容器初始化完成然后docker logs mysql8 | tail -20看到 “ready for connections” 字样就是初始化完成。然后用容器内自带客户端连接docker exec -it mysql8 mysql -uroot -p进入 MySQL 后执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY Root123; CREATE USER app% IDENTIFIED BY App123; GRANT ALL PRIVILEGES ON *.* TO app%; FLUSH PRIVILEGES;这几个 SQL 指令是必做的因为 MySQL 8.0 默认 root 只允许 localhost 登录不开放远程访问宿主机之外的程序根本连不上。开发环境这三个指令搞定生产环境建议把 root 远程登录关掉用独立账号。5.2 Redis 主从部署一条 run 指令不够得组合着来Redis 主从也是热搜词里的高频需求。主从逻辑其实很简单一个主库负责写一个或多个从库负责同步读压力可以打到从库上。先起主库docker run -d \ --name redis-master \ -p 6379:6379 \ -v /data/redis/master:/data \ redis:7.0 redis-server --appendonly yes再起从库docker run -d \ --name redis-slave \ -p 6380:6379 \ -v /data/redis/slave:/data \ redis:7.0 redis-server --appendonly yes --slaveof 宿主机IP 6379注意从库的 --slaveof 后面要填主库容器的 IP 或宿主机 IP而不能填 localhost。因为从库容器里的 localhost 指向它自己。如果你把主库和从库放在同一个 docker 网络里这里直接写主库容器名更优雅docker network create redis-net docker run -d --name redis-master --network redis-net \ -p 6379:6379 \ -v /data/redis/master:/data \ redis:7.0 redis-server --appendonly yes docker run -d --name redis-slave --network redis-net \ -p 6380:6379 \ -v /data/redis/slave:/data \ redis:7.0 redis-server --appendonly yes --slaveof redis-master 6379验证主从同步进从库容器查docker exec -it redis-slave redis-cli -p 6379 info replication看到 role:slave 和 master_link_status:up说明同步链路正常。这里有个实操心得不要把所有数据都丢默认位置appendonly yes 一定要开不然 Redis 默认按快照方式持久化你最多只保留最近一段时间的全量数据增量日志完全没有。6. 常用指令速查表从“背不会”到“随时瞟一眼”指令这种东西没人能全记在脑子里关键是知道“有这样一个指令能解决那个问题”。我整理了一张速查表贴在终端旁边用到什么查什么。场景指令说明查看所有容器docker ps -a加 -a 显示已退出的容器启动/停止/重启docker start/stop/restart 容器名常用三兄弟进入容器docker exec -it 容器名 bash/shAlpine 等精简镜像用 sh查看实时日志docker logs -f 容器名CtrlC 退出跟踪查看镜像列表docker images关注 TAG 与 IMAGE ID删除容器docker rm -f 容器名-f 强制删除运行中容器删除镜像docker rmi 镜像ID先删容器才能删镜像清理垃圾docker system prune -a一键清空无用资源构建镜像docker build -t 名称:标签 .别忘最后的点拉取镜像docker pull 镜像名:版本生产环境指定版本查看资源占用docker stats类似 top 指令查看容器配置docker inspect 容器名输出 JSON 全集端口映射查看docker port 容器名快速确认映射关系复制文件docker cp 宿主机文件 容器名:/路径可进可出双向复制查看容器IPdocker inspect -f {{.NetworkSettings.IPAddress}} 容器名自定义网络下建议用容器名替代这张表里我特意加了 docker stats 和 docker inspect 两个很多新手不熟但排障时极其有用。有一次我服务响应慢一看 docker stats 发现容器 CPU 用了 120%直接定位到代码死循环省了大半天排查时间。7. 拆分 docker-compose 指令多容器编排从“手打命令”到“YAML 搞定”7.1 为什么有了 run 指令还需要 Compose单容器用 docker run 完全够但一旦涉及“MySQL Redis 后端服务 前端服务”这种组合你每次启动都要敲一长串命令敲错一个参数就要全部重来。而且容器之间的网络、挂载、依赖启动顺序全都要手动管这工程就变成灾难了。Docker Compose 的玩法是把所有容器的定义写进 docker-compose.yml一条指令启动全部、停止全部。先确认环境里有没有docker compose version老版本要另装 docker-compose新版本 Docker Desktop 内置了 docker compose直接用。7.2 一个最小可用的 compose 文件MySQL Redis下面这个文件我用过无数次直接抄就能跑version: 3.8 services: mysql: image: mysql:8.0 container_name: app-mysql restart: always environment: MYSQL_ROOT_PASSWORD: Root123 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql redis: image: redis:7.0 container_name: app-redis restart: always command: redis-server --appendonly yes ports: - 6379:6379 volumes: - redis_data:/data volumes: mysql_data: redis_data:启动命令docker compose up -d查看状态docker compose ps停止并删除容器数据卷默认保留docker compose down要是想连数据卷一起删干净docker compose down -v我特别提醒一句-v 会删数据卷MySQL 的数据会全没。每次在生产环境执行前都先确认自己是不是真想删数据。7.3 用 compose 管理自定义网络和依赖启动顺序多服务之间经常存在先后依赖比如后端要等 MySQL 就绪了才能连上。Compose 里有 depends_on 可以控制启动顺序services: backend: build: ./backend ports: - 8080:8080 depends_on: - mysql - redis这里只保证容器启动顺序并不保证 MySQL 的初始化完成了。真要稳妥应用侧要做重试连接或者用 healthcheck 指令services: mysql: image: mysql:8.0 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5然后 backend 的 depends_on 加上 conditiondepends_on: mysql: condition: service_healthy这套配置下来backend 会等 MySQL 健康检查通过了才启动基本杜绝“数据库没起来应用疯狂报错”的尴尬。8. 常见问题与排查实录这些坑每个都真实踩过8.1 Docker Desktop 在 Windows 上启动失败virtualisation support not detected热搜题里反复出现的 “virtualization support not detected”我帮同事处理过好几次。问题本质是 Docker Desktop 依赖 Windows 的虚拟化功能但你的机器没开启或没启用对应的 Windows 功能。排查步骤按顺序来任务管理器 - 性能 - CPU看右下角“虚拟化”是否显示“已启用”。如果显示“已禁用”进 BIOS 开启 Intel VT-x 或 AMD-V。不开 BIOS 也行的临时方案控制面板 - 启用或关闭 Windows 功能把“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两个勾上然后重启。重启完还是不行以管理员身份运行 PowerShell执行wsl --status确认 WSL 版本是 2。如果是 WSL 1执行wsl --set-version 发行版名 2还有一个小坑某些电脑开了 Hyper-V 和 WSL2 冲突。这种情况下 BIOS 里的 VT-x 必须开着Hyper-V 反而可以关掉。Docker Desktop 现在默认用 WSL2 后端Hyper-V 不是必需。8.2 容器网络不通先查网络再查防火墙“docker 网络不通”这个问题我在搜索词里看到了也每天在群里看到新手问。最典型的表现是宿主机能访问容器映射的端口但容器之间互相访问超时。第一步先确认容器在哪个网络里docker inspect 容器名 | grep NetworkMode第二步如果容器属于不同网络把它们加入同一个自定义网络docker network connect mynet 容器名第三步如果已经是同一个网络进容器里测试连通性docker exec -it 容器名 ping 其他容器名如果 ping 不通看看容器里有没有安装 ping 工具有些精简镜像没有不能据此判断网络不通。更靠谱的测试是用应用层的端口探测docker exec -it 容器名 curl http://目标容器名:端口还有一个容易被忽略的是防火墙。云服务器上安全组没放行对应端口宿主机 firewalld 或 ufw 拦住了映射端口都会导致外部访问失败。如果是本地虚拟机玩 Docker记得把虚拟机的网络模式改成桥接NAT 模式下外部机器访问进来很麻烦。8.3 MySQL 容器安装失败日志里已经把答案告诉你了“docker 安装 mysql 失败”这种问题我处理过太多次了。先说最常见的三种原因第一是 root 密码策略太弱。MySQL 8.0 默认密码校验强度要求大小写字母数字混合你设置 MYSQL_ROOT_PASSWORD123456 很容易初始化失败。解决办法是加环境变量-e MYSQL_ROOT_PASSWORDRoot123456或者用 MYSQL_ALLOW_EMPTY_PASSWORDyes 临时空密码进去再改。第二是挂载目录权限不对。容器内 mysql 用户 UID 是 999宿主机目录是 root 所有容器无法写入数据目录。解决办法chown -R 999:999 /data/mysql第三是端口冲突。宿主机的 3306 已经被本机 MySQL 或其他容器占了docker run 直接报 bind: address already in use。用 docker ps 查看是哪个容器占了端口或者换一个宿主端口映射-p 3307:3306最关键的一招大量失败信息都在 docker logs 里先看日志再百度。有 70% 的问题日志里直接写了原因剩下 30% 才是环境配置问题需要你自己慢慢排查。8.4 容器清理的节奏感别在不该清的时候按下 system prune我见过一个同事为了“清理磁盘”在生产环境上执行了 docker system prune -a结果所有没在运行的镜像全没了回滚的时候发现没有本地镜像只能重新拉取旧版本而那版本在私有仓库里早被覆盖了。教训就是生产环境不清理或者清理之前一定要确认镜像可以从可靠渠道重新获取。开发环境我倒是建议大家养成周期性清理每周跑一次docker system prune -af --volumes-a 清理所有未使用的镜像-f 跳过确认提示--volumes 顺手清掉没被容器使用的数据卷但这命令的破坏力极大跑之前最好先做一次“演练”docker system df这个指令会显示磁盘占用分布哪些是镜像、哪些是容器、哪些是数据卷一目了然。你先看数据再决定清什么别从头到尾一把梭。9. 我个人在实际操作中的几个体会写了这么多其实最想跟大家分享的并不是具体命令而是使用 Docker 的思维方式。第一容器是“宠物”还是“牲口”决定了你的操作方式。如果你把每个容器当成随时可以重建的“牲口”你就不会花时间进去手动改配置而是改完重新 build 再 run。反过来要是把容器当“宠物”养手动进去乱改一通下一次 rebuild 的时候所有手工操作全部丢失哭都来不及。第二指令报错的排查路径永远是“日志优先”。docker logs、docker inspect、docker ps -a、docker stats这四个指令能解决 90% 的日常问题。不要一报错就怀疑镜像有问题先看日志把日志贴出来再问人没人能靠一句“我 run 失败了”帮你定位问题。第三把反复敲的指令沉淀成 Compose 文件或脚本。我发现凡是手动敲过三次以上的 docker run都值得变成 docker-compose.yml。这不是懒是从制度上杜绝敲错参数。把基础设施写成代码不管换机器还是换同事都能保持一致的部署结果。最后再多说一个小技巧给容器命名的时候花点心思用有意义的项目名加角色比如 app-mysql、app-redis、app-backend而不是默认生成的一串随机 ID。等你的宿主机上跑了几十个容器docker ps 的输出一眼能认出谁是谁的时候你会感激自己这个好习惯。
阅读完成 · 觉得有帮助?
咨询建站