
刚接触 Docker 那阵子我最常干的事就是把docker run的长参数串反复复制到笔记里等真到要创建容器时才发现要么端口映射漏了、要么数据目录没挂载、要么容器一重启数据全没。这篇笔记就是围绕“Docker 创建容器”这件事把我自己踩过的坑和常用命令整理成一套完整思路先搞清楚镜像和容器的关系再弄明白创建容器的几种方式然后把参数逐项拆开讲最后给几个高频实战场景和常见报错的排查方法。适合刚装好 Docker 但还不知道怎么把镜像跑成容器的人也适合已经会docker run但想系统补全参数细节的人。1. 从核心思路说起为什么创建容器要先搞懂“镜像与容器”的关系1.1 docker run 到底干了什么很多人第一次用docker run nginx看到镜像被下载、容器直接能访问时会觉得 Docker 就是个“一键装软件”的工具。但实际创建容器的过程比你看到的要复杂一点。一条docker run命令背后Docker 会做四件事检查本地有没有指定镜像没有就自动去仓库拉取基于镜像创建一个可写的容器层相当于把镜像变成“一个正在运行的实例”分配网络、挂载卷、设置环境变量等运行时配置执行镜像里预设的启动命令比如 MySQL 镜像里的mysqld理解了这四步你再看创建容器的命令就不会把参数当成“照抄的咒语”了。比如-p 3306:3306是网络层面的配置-v /data:/var/lib/mysql是存储层面的配置-e MYSQL_ROOT_PASSWORD123456是环境变量层面的配置它们分别在容器的不同生命周期阶段生效。1.2 创建容器的四种常见方式我在实际使用中创建容器基本只用到四种方式各有适用场景docker create只创建容器但不启动适合先准备好容器、稍后再启动或者需要批量创建的场景docker run创建并立即启动这是用得最多的方式docker compose up通过 YAML 文件创建并启动多个容器适合多服务编排先docker create再docker start适合调试阶段创建后先检查配置确认没问题再启动这四种方式本质都是“由镜像生成容器”区别只在启动时机和管理粒度。单容器调试用docker run多服务部署用 compose需要精细控制启动时机就用docker create加docker start。2. 实战准备Docker 安装与基础环境检查2.1 安装 Docker Desktop 还是纯 Docker Engine创建容器之前你得先有一个能用的 Docker 环境。现在主流两种选择Windows/Mac 上用 Docker DesktopLinux 上装 Docker Engine。Docker Desktop 自带图形界面还能在设置里方便地调整资源配额比如给 Docker 分配多少内存和 CPU。但新手最容易卡在 Docker Desktop 的启动上因为它依赖系统的虚拟化支持。如果你在 Windows 上遇到 “Docker Desktop failed to start because virtualisation support wasnt detected” 这类报错通常不是 Docker 本身的问题而是 BIOS 里的虚拟化没开。我的建议是Windows 用户优先用 Docker Desktop遇到启动失败先开 BIOS 的虚拟化Linux 用户直接装 Docker Engine命令方式更纯粹也更好排查问题。Linux 上安装 Docker Engine 最简单的方式是用官方脚本curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本会帮你配好软件源、把 Docker 装好并设置开机自启。装完后可以用docker --version验证。Windows 上则直接下载 Docker Desktop 安装包按提示走完向导装完一般需要重启或至少退出重进一次让 Docker 的虚拟化服务生效。2.2 基础状态检查别一上来就 run我见过不少新手Docker 刚装完就急着docker run结果报一堆错分不清是镜像问题还是环境问题。正确的顺序是先做三个基础检查# 检查 Docker 版本 docker --version # 检查 Docker 服务是否正常能显示出系统信息的输出 docker info # 检查当前有哪些容器在跑 docker psdocker info很值得养成习惯。如果这里就报错docker run大概率也会挂。常见的失败情况有当前用户不在 docker 用户组里Linux 下会提示权限不足、Docker Desktop 虚拟机没启动、磁盘空间不够。2.3 镜像准备先看本地有没有再考虑拉取创建容器前确认镜像是否存在是个好习惯。我用 Docker 这些年总结出一条经验尽量显式指定镜像版本别默认用 latest。# 查看本地已有的镜像 docker images # 拉取指定版本镜像比如 MySQL 8.0 docker pull mysql:8.0 # 拉取最新版 docker pull nginx:latest为啥我强调指定版本latest是个移动标签今天拉到的镜像和三个月后拉到的可能完全不同。你写博文、做项目、部署生产环境时版本不固定等于给自己埋雷。比如安装 MySQL 8.0 时我通常直接docker pull mysql:8.0这样后面创建容器时用的是确定的版本排查问题也有据可依。3. 创建容器的核心命令详解3.1 docker create只建不跑的场景docker create字面意思就是“创建容器”但执行完容器处于 created 状态不会启动。我第一次用这个命令时觉得有点鸡肋后来发现它在两个场景下特别好用需要预先分配一个固定名称或固定 IP 的容器但不希望它马上运行想先创建容器、检查配置再启动方便调试命令格式如下docker create --name my-nginx -p 8080:80 nginx:latest后面接的参数和docker run完全一样区别就是最后不自动启动。创建完可以用docker start my-nginx把容器跑起来。如果你拿不准一个容器怎么配参数用docker create先创建再用docker inspect检查配置比直接docker run翻车了再改要安全。3.2 docker run最常用的一步到位docker run是所有创建容器命令里最核心的。基本语法是docker run [OPTIONS] IMAGE [COMMAND] [ARG...]一个最朴素的示例docker run --name web -d -p 8080:80 nginx:latest这条命令的拆解是容器名叫 web后台运行-d把宿主机的 8080 端口映射到容器内的 80 端口用 nginx 镜像创建并启动。这里有个容易忽略的细节[COMMAND]这部分可以覆盖镜像默认的启动命令。比如镜像里默认启动 nginx你想只启动一个 bash 做调试就可以这样docker run -it --name debug nginx:latest bash-it是-i和-t的组合-i保持标准输入打开-t分配一个伪终端。组合起来你就能像 SSH 到一台机器一样直接进入容器的 shell。注意这种方式进去后容器里只是个 bashnginx 服务并不会启动适合排查问题不适合正式跑服务。3.3 常用参数逐项拆解创建容器时参数选得对不对直接决定你后面用起来顺不顺。我把高频参数按用途分成了几组。资源限制相关的-m或--memory限制容器最大内存比如-m 512m--cpus限制容器可用 CPU 数比如--cpus1.5--restart容器退出时的重启策略always表示只要 Docker 服务在容器挂了就自动拉起网络相关的-p 宿主机端口:容器端口端口映射-P随机映射容器内所有暴露的端口--network指定网络模式比如--network host直接用宿主机网络--ip指定容器 IP需要配合自定义网络使用存储相关的-v 宿主机目录:容器目录数据卷挂载--mount typebind,source宿主机目录,target容器目录另一种挂载语法更喜欢用这个因为语义更清晰环境与交互相关的-e或--env设置环境变量--env-file从一个文件读取环境变量-it交互式终端-d后台运行--name指定容器名称这么多参数新手最容易混乱的是-v的挂载写法。它的核心逻辑是宿主机目录在冒号左边容器目录在冒号右边。比如把宿主机的/home/myapp/data挂到容器的/var/lib/mysql就写-v /home/myapp/data:/var/lib/mysql。3.4 端口映射、数据卷、网络这三个关键点创建容器为什么要把这三样单独拎出来因为它们分别对应容器使用时的三个老大难问题怎么访问、数据怎么保存、容器之间怎么通信。端口映射的问题在于宿主机端口不能重复。你想创建两个 nginx 容器宿主机 80 端口只能给一个用另一个就得换8080或者别的。养成“端口冲突先查docker ps看看自己开了哪些端口”的习惯能少踩很多坑。数据卷的问题在于容器删除后容器层的数据就没了。如果你跑一个 MySQL 容器不加-v挂载数据目录容器一删数据库就没了。我见过很多次这种惨案。解决方案就是挂载数据卷让数据存在宿主机上和容器生命周期解耦。网络的问题在于多个容器之间怎么互相访问。最简单的方式是用--link但那是老古董了现在推荐先创建一个自定义网络然后让容器加入同一个网络互相用容器名通信docker network create mynet docker run --name mysql8 --network mynet -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0 docker run --name myapp --network mynet -d my-webapp:v1这样myapp容器里访问数据库直接用mysql8:3306就行不需要关心 MySQL 容器的 IP 是多少。4. 容器创建后的生命周期管理4.1 启动、停止、重启和查看状态创建完容器后续管理基本围绕生命周期操作展开。常用命令不多但每个都很重要# 查看运行中的容器 docker ps # 查看所有容器包括已经停止的 docker ps -a # 启动一个已存在的容器 docker start 容器名或ID # 停止容器 docker stop 容器名或ID # 重启容器 docker restart 容器名或ID这里我多说一句docker stop是优雅停止会先给容器发 SIGTERM 信号等一段时间再发 SIGKILL。如果你想让容器立刻死掉就用docker kill。日常操作里stop基本够用kill一般只在容器卡死时用。docker ps -a这个命令很重要。你刚才用docker run创建了容器但是docker ps看不到多半是容器启动后立刻退出了。这时候docker ps -a就能看到这个容器的状态是 Exited再配合docker logs查看退出原因。4.2 进入容器、执行命令、查看日志进入容器有两种常见方式我都经常用# 方式一直接在容器里开一个交互式终端 docker exec -it 容器名 bash # 方式二执行单条命令不进入交互环境 docker exec 容器名 mysql --versionexec和run的-it区别很多人会混。docker exec -it 容器名 bash是在一个已经运行的容器里开新进程而docker run -it是创建新容器并进入。前者你进入的是“当前这台运行中的机器”后者你是“从镜像重新开一台机器”。容器日志查看也有固定套路# 查看全部日志 docker logs 容器名 # 实时跟踪日志输出按 CtrlC 退出 docker logs -f 容器名 # 看最后 100 行日志 docker logs --tail 100 容器名我排查问题时几乎都是docker logs 容器名 | tail -50这种组合先把最近报错捞出来再逐行分析。日志是容器给外界的唯一“发言渠道”很多启动失败的原因都藏在日志尾部。4.3 删除容器与清理删除容器这个操作一定要和数据卷搞明白关系。# 删除一个停止的容器 docker rm 容器名 # 强制删除运行中的容器 docker rm -f 容器名 # 删除容器时同时删除它的匿名数据卷 docker rm -v 容器名如果你用了-v显式挂载宿主机目录容器删除后宿主机目录里的数据还在。但如果你创建容器时没挂载数据卷容器写入的数据会存在匿名数据卷里docker rm默认不会删除这些卷不手动清理会一直占磁盘。常用的清理命令# 清理所有停止状态的容器 docker container prune # 清理所有悬空镜像没有标签且没有被容器引用的镜像 docker image prune # 一键清理容器、网络、悬空镜像、构建缓存 docker system prunedocker system prune很猛会把你不要的镜像缓存全部清掉。我第一次跑这个命令时手一抖把本地一堆没用但以后可能用到的镜像都清了后来学乖了先docker system prune -a前看清楚提示别盲目加-a。5. 实操案例MySQL 8.0、Redis 主从、青龙依赖管理5.1 MySQL 8.0 创建容器全流程MySQL 容器是我创建最多的容器类型之一。网络热搜词里“docker安装mysql失败”“docker安装mysql8.0并使用”出现频率很高说明很多人卡在创建这一步。我完整走一遍流程。第一步拉取镜像并创建数据目录docker pull mysql:8.0 mkdir -p /data/mysql第二步创建容器docker run --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyPass123 \ -v /data/mysql:/var/lib/mysql \ -v /data/mysql-conf:/etc/mysql/conf.d \ --restartalways \ -d mysql:8.0这一步有几个关键点MYSQL_ROOT_PASSWORD是初始化时设置 root 密码的环境变量/var/lib/mysql是 MySQL 的数据目录必须挂载出来否则删容器等于删数据库/etc/mysql/conf.d挂载配置文件目录这样可以把自己写的my.cnf放进去比如修改字符集第三步验证容器和数据库是否正常docker ps docker logs mysql8 | tail -20 docker exec -it mysql8 mysql -uroot -p如果提示Cant connect to local MySQL server through socket这类错误多半是mysqld没起来或初始化没完成。docker logs mysql8里能看到“ready for connections”说明初始化成功了。初始化过程一般需要几十秒刚创建完别急着连。第四步访问容器内的 MySQL。如果是在宿主机上连直接mysql -h127.0.0.1 -P3306 -uroot -p就行。如果是在另一个容器里访问就要像前面说的把两个容器放在同一个自定义网络里用容器名做主机名。我踩过的坑MySQL 8.0 默认认证插件是caching_sha2_password老客户端工具可能连不上会报认证错误。如果你非要用老客户端得在创建容器时加一条参数--default-authentication-pluginmysql_native_password但这种方式在新版本 MySQL 里已经逐渐移除了更推荐升级客户端去兼容新插件。5.2 Redis 主从复制容器创建Redis 做主从Docker 创建容器时最方便的方式是先启动一个主节点再启动从节点并指定主节点地址。主节点docker run --name redis-master \ -p 6379:6379 \ -v /data/redis-master:/data \ -d redis:6.0 \ redis-server --appendonly yes从节点docker run --name redis-slave \ -p 6380:6379 \ --network mynet \ -v /data/redis-slave:/data \ -d redis:6.0 \ redis-server --appendonly yes --slaveof redis-master 6379这里注意从节点要能解析到主节点必须和主节点在同一个 Docker 网络里。--slaveof redis-master 6379的参数里redis-master是主节点的容器名Docker 内置 DNS 会把它解析成主节点容器的 IP。验证主从是否成功docker exec -it redis-master redis-cli info replication docker exec -it redis-slave redis-cli info replication主节点输出里connected_slaves:1从节点输出里role:slave且master_link_status:up就说明主从搭建成功。这个场景里最容易出的问题是从节点刚起来时日志报MASTER - REPLICA sync started然后看到Master did not respond to PING这种错误。绝大多数情况都是网络不对要么两个容器不在同一个自定义网络要么端口映射绑定的地址不对。5.3 青龙面板的依赖管理青龙qinglong是一个跑定时脚本的常见容器应用很多人在“依赖管理”上吃过亏。创建青龙容器本身不复杂docker run --name qinglong \ -p 5700:5700 \ -v /data/qinglong:/ql/data \ -d whyour/qinglong:latest创建成功后在浏览器打开http://宿主机IP:5700按提示初始化账号密码就行。但青龙跑脚本时经常缺系统依赖比如 Python 包、Node 包、Linux 库。很多人创建容器后直接进容器手动apk add或pip install然而容器一重建这些全没了。我的做法是把依赖安装命令固化到容器启动后的初始化脚本里或者用额外的-v挂载把依赖缓存目录也映射出来。比如让 pip 缓存留在宿主机-v /data/qinglong/pip-cache:/root/.cache/pip这样即使容器重建重新安装依赖时还能命中宿主机上的缓存下载速度提升明显。依赖管理这件事关键思路是容器的文件系统是临时的任何要持久化的东西都要靠挂载。我见过有人直接把整个/ql/data目录挂出来也包括了依赖所在路径这样容器重建后脚本依赖还在。但因为依赖跟运行环境强相关系统镜像一升级原来的依赖还是可能失效所以更推荐在容器初始化脚本里声明依赖实现可重复安装。6. 常见问题与排查技巧实录6.1 Docker Desktop 启动失败虚拟化未开启很多 Windows 用户装完 Docker Desktop双击启动就弹窗“Docker Desktop failed to start because virtualisation support wasnt detected”。这个报错信息我在热搜词里看到也是高频。原因基本只有一个BIOS 里的 CPU 虚拟化没开。排查顺序打开任务管理器切到“性能”选项卡看“虚拟化”字段是“已启用”还是“已禁用”如果已禁用重启电脑进 BIOS找到 Intel VT-x 或 AMD-V 选项并启用保存退出后重新启动 Docker DesktopWindows 系统还需要保证“Windows 虚拟机监控程序平台”和“适用于 Linux 的 Windows 子系统”这两个功能是开着的。可以在控制面板的“启用或关闭 Windows 功能”里勾选然后重启。我有个朋友就是卡在这一步最后发现是公司电脑的 BIOS 被统一锁了虚拟化找 IT 解锁才解决。如果你也遇到这种情况可以先检查是不是系统层面的权限策略挡着。6.2 创建 MySQL 容器失败排查思路MySQL 容器在创建阶段失败的几个常见原因我整理成一个排查顺序docker logs mysql8看日志如果出现data directory ... is not empty说明挂载的数据目录里已经有数据和镜像初始化逻辑冲突端口被占用报错里会出现port is already allocated用docker ps看看是不是已有容器占用了 3306磁盘空间不足拉取镜像或容器写入数据时都会报no space left on device权限问题宿主机挂载目录的属主不对MySQL 写不进去日志里常见Permission denied遇到 MySQL 初始化失败我基本不看网上杂七杂八的文章直接照着日志一步步排查。容器技术的好处就是问题会直接暴露在日志里比原生安装好定位得多。另外有一个非常容易忽略的点--restartalways加上后容器每次退出都会被 Docker 拉起来如果服务一直崩溃容器会反复重启。此时先用docker inspect看容器的RestartCount能帮你判断是不是真的“反复崩溃”。6.3 容器网络不通的排查套路“docker网络不通”是热搜词里的高频词我分享一个自己的排查方法清单# 先看容器是否在一个网络里 docker inspect 容器名 | grep -i network # 查看容器 IP docker inspect -f {{.NetworkSettings.IPAddress}} 容器名 # 进入容器测试网络连通性 docker exec -it 容器名 ping 目标IP # 查看当前所有网络 docker network ls最常见的网络问题原因是宿主机网络模式--network host加了之后端口映射-p就不生效了。host 模式下容器直接用宿主机网络不需要也不能用端口映射。如果你用 host 模式发现访问不了容器服务优先想想是不是端口绑定的问题而不是继续加参数。如果容器之间要互相访问默认的 bridge 网络其实也能通信但需要靠 IP容器重建后 IP 会变。更推荐的方式就是前面说过的自定义网络容器名即主机名稳定省心。6.4 时区、编码和文件权限的隐藏坑容器创建完成后经常遇到时区不对的问题。很多官方镜像默认时区是 UTC和北京时间差 8 小时。如果你在容器里跑定时任务或者看日志时间会非常别扭。解决办法是在创建容器时加一条时间挂载或者用环境变量# Linux 下推荐直接挂载时区文件 -v /etc/localtime:/etc/localtime:ro # 或者用环境变量部分镜像支持 TZ -e TZAsia/Shanghai有些镜像支持TZ环境变量有些不支持所以-v /etc/localtime这种挂载方式更通用。Windows 上因为没有/etc/localtime通常只能靠TZ环境变量。文件权限的问题则更多出现在挂载目录的时候。容器里的进程以 root 身份运行没问题但如果你的宿主机目录权限太严容器里创建的文件会写到宿主机会变成 root 所有宿主机普通用户没法改。经常见到宿主机上没法删容器生成的文件的问题就是这个原因。6.5 创建容器时的常用排查命令速查我把日常排查创建容器故障的高频命令整理成一张速查表方便照着敲目标命令查看容器详细配置和状态docker inspect 容器名查看容器启动以来重启的次数docker inspect -f {{.RestartCount}} 容器名查看容器 CPU 和内存占用docker stats 容器名查看容器日志尾部 100 行docker logs --tail 100 容器名查看所有容器含停止docker ps -a查看容器的挂载卷和网络配置docker inspect 容器名 -f {{json .Mounts}}查看容器的环境变量docker inspect 容器名 -f {{json .Config.Env}}进入容器交互界面docker exec -it 容器名 bash我排查创建容器故障时固定套路就是docker ps -a看状态docker logs看日志docker inspect看配置。这三步走完八成问题都能定位到。7. 个人经验总结与建议最后分享几点我自己的体会。创建容器这件事核心其实不是“记命令”而是理解容器从镜像到运行时的过程。docker run后面的参数本质上是你在回答三个问题容器怎么被访问、数据怎么存、和谁怎么通信。把这三个问题想清楚参数自然就记住了。另外我特别建议把“创建容器的命令”用脚本或 Makefile 管理起来。比如在项目里放一个run.sh把docker run的长命令写进去这样下次重建容器时不用翻历史记录直接跑脚本就行。自己写脚本时顺便把版本号写死不要用 latest时间久了你就懂这个习惯有多重要了。最后再分享一个调试小技巧当你拿不准某条docker run命令会创建出什么容器时可以先用docker create代替docker run创建完用docker inspect看看配置对不对再手动docker start。等确认无误后再放回正式脚本里用。这样能少踩很多因为参数写错导致的返工坑。如果你感觉创建容器这台词里最麻烦的还是数据卷挂载就拿 MySQL 和 Redis 各练一遍把容器删了再重建数据都还在你就真的理解了“数据卷解耦容器生命周期”这句话。