
1. Docker是什么先搞清楚它到底解决什么问题想弄懂Docker先回忆一下以前部署一个应用有多折腾装完系统还得装Python或者Java运行时装数据库装各种依赖库版本对不上直接白给。新同事入职光配环境就得浪费一整天。换台服务器整个流程再走一遍。最难受的是开发环境跑得好好的代码一上测试环境就崩最后排查半天发现是某个系统库版本不一样。Docker就是来解决这个痛点的。它的核心思路一句话能说清把应用连同它的运行环境一起打包成一个标准化的“集装箱”这个集装箱就是镜像运行起来的集装箱实例就是容器。不管底层是什么操作系统只要装了Docker引擎容器一启动里面跑的就是一模一样的环境。这带来的好处非常直接环境一致性开发、测试、生产环境完全一致告别“在我电脑上明明是好的”。秒级启动容器本质上是宿主机上的进程启动一个容器最快几百毫秒比虚拟机动辄几十秒的开机快得多。资源隔离每个容器有自己的文件系统、网络、进程空间互不干扰但共享宿主机内核所以比虚拟机轻量得多。一键交付打包好的镜像直接推到仓库部署时拉下来运行即可跟搬集装箱一样标准化。如果你在Windows上做开发想装MySQL、Redis、GitLab这些基础设施Docker Desktop基本就是开箱即用的工具箱。如果是Linux服务器那就更顺了Docker本来就是为Linux而生的。这篇文章我不会只丢一堆命令行而是从概念到实战把Docker最核心的用法拆开讲清楚。跟着操作一遍你至少能把MySQL和Redis部署起来还会用Docker Compose做编排。文章最终的目标是让你读完能独立完成日常开发中的容器化部署而不是死记硬背命令。2. 安装Docker环境每一步都是坑2.1 Windows安装虚拟化报错怎么解决Windows上安装Docker Desktop最经典的报错就是Docker Desktop failed to start because virtualization support wasnt not detected翻译过来就是“没检测到虚拟化支持”。这个问题我见过太多人卡在这里其实根因和解决思路都很清晰。先明确一点Docker Desktop在Windows上依赖WSL2或者Hyper-V来运行Linux虚拟机因为Docker容器运行在Linux内核之上。如果BIOS里没开启虚拟化或者Windows的虚拟机平台功能没打开就会报上面的错。排查步骤按顺序来打开任务管理器切到“性能”选项卡看右下角“虚拟化”是不是“已启用”。如果显示“已禁用”就得进BIOS开启。不同品牌的电脑BIOS入口不一样最常见的是开机时按Del或F2找到Intel Virtualization Technology或者SVM Mode设置为Enabled保存重启。重启后再看任务管理器确认虚拟化已启用。如果BIOS没问题但还是报错那就是Windows功能没开。执行这一步# 以管理员身份打开PowerShell执行 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart执行完重启电脑再装Docker Desktop。装完以后如果提示WSL2内核版本太旧去微软官网下载最新的WSL2内核更新包装上。还有一类情况是电脑支持虚拟化但被其他虚拟机软件占用了比如老版本的VirtualBox和Hyper-V不共存这种情况要么卸载老软件要么在Docker Desktop的设置里把引擎切到WSL2。2.2 Linux安装一条命令背后的细节Ubuntu和CentOS是服务器上最常见的两个发行版。Ubuntu上安装最主流的方式是用官方脚本curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本会自动检测系统版本装好Docker引擎、命令行工具和containerd过程比较省心。脚本跑完以后Docker服务会自动启动并设置开机自启。CentOS 7的安装稍微古董一点但网上大量生产服务器还是CentOS 7经常有用户问怎么升级所以也说一下。CentOS 7的Docker版本和Ubuntu不同一般通过yum安装# 安装依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # 配置Docker仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker引擎 sudo yum install -y docker-ce docker-ce-cli containerd.io # 启动并设置开机自启 sudo systemctl start docker sudo systemctl enable dockerCentOS 7如果要从旧版本Docker升级直接执行sudo yum update docker-ce docker-ce-cli即可但建议先备份现有镜像和容器数据升级前最好用docker commit把改动过的容器转成镜像防止升级后起不来。权限问题是Linux新手最常见的坑。装好Docker后直接执行docker ps可能会报Got permission denied while trying to connect to the Docker daemon socket原因很好理解Docker守护进程的socket文件只有root用户有权限访问。解决办法有两个每次命令前面加sudo简单粗暴但不方便。把当前用户加入docker组一劳永逸sudo usermod -aG docker $USER newgrp docker执行完后重新登录再执行docker ps就不需要sudo了。注意不要随便把用户加入docker组——这个组里的用户等于拥有root级别的权限因为容器可以挂载宿主机目录存在安全风险。2.3 镜像源配置下载慢的解决办法国内下载Docker镜像慢几乎是每个刚入门的人都会遇到的问题。用默认源拉取一个几百MB的镜像可能等上十分钟还卡在等待状态。解决办法是配置镜像加速器。Linux上编辑/etc/docker/daemon.json{ registry-mirrors: [https://你的加速器地址] }Windows上直接打开Docker Desktop的Settings找到Docker Engine选项在里面修改同样的JSON配置。改完以后重启Docker服务生效。需要注意的是加速器只对Docker Hub上的公共镜像有效如果拉取的是私有仓库镜像还是走仓库本身的网络。镜像加速的原理是Docker拉取镜像时会优先从配置的镜像仓库下载但这些仓库只是Docker Hub的缓存节点。实测下来配置加速后拉取常用镜像的速度能快十倍以上基本能达到几MB每秒的下载速度。3. 核心概念与常用命令看完就懂3.1 镜像、容器、仓库Docker有三个最基础的概念用生活化的类比来理解镜像Image相当于一个模板或者安装包。镜像是只读的包含了一个完整的运行环境代码、运行时、系统库、配置。可以说镜像就是集装箱的设计图纸。容器Container镜像运行起来的实例。相当于按照图纸造出来的集装箱可以启动、停止、删除。多个容器可以共享同一个镜像容器之间互相隔离。仓库Repository存放镜像的地方类似代码的Git仓库。最著名的是Docker Hub构建好的镜像推上去别人拉下来就能用。这三个概念的关系可以这样记镜像定义“装什么”容器决定“跑什么”仓库解决“存哪里”。3.2 常用命令速查命令不用死记常用的就这么多# 镜像操作 docker images # 查看本地镜像列表 docker pull nginx:latest # 拉取镜像建议指定版本号而不是latest docker rmi 镜像ID或名称 # 删除镜像 docker build -t 镜像名:版本 . # 基于Dockerfile构建镜像 # 容器操作 docker ps # 查看运行中的容器 docker ps -a # 查看所有容器包括已停止的 docker run -d --name 容器名 镜像名 # 后台运行容器 docker start/stop/restart 容器名 # 启动/停止/重启容器 docker rm 容器名 # 删除容器 docker exec -it 容器名 bash # 进入容器内部 docker logs -f 容器名 # 查看容器日志 docker inspect 容器名 # 查看容器详情docker run的参数才是真正的重点配置一个容器通常要组合好多个参数docker run -d \ --name mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v /opt/data:/var/lib/mysql \ mysql:8.0-d后台运行--name容器命名管理时有个好名字比记ID强多了-p端口映射宿主机端口:容器内端口-e环境变量MySQL容器必须指定root密码-v数据卷挂载把容器内的数据目录映射到宿主机容器删了数据还在-p端口映射是最容易想不通的参数。容器内部有自己独立的网络空间宿主机访问不到容器的3306端口除非做了端口映射。-p 3306:3306意味着访问宿主机的3306端口流量就转发到容器的3306端口。前一个是宿主机端口后一个是容器内端口务必记住别搞反。再补充一点docker run的--restart参数在生产环境很有用--restart always # 容器退出后自动重启包括Docker服务重启后也会自动拉起跑数据库之类的容器时这个参数几乎是标配不然服务器一重启容器全挂了还得手动拉起来。3.3 数据卷挂载为什么必须用容器最容易被新手忽略的问题是容器一旦删除里面产生的数据全部丢失。因为容器写入层和容器生命周期绑定容器没了数据就没了。解决办法就是数据卷挂载Volume把容器内的目录映射到宿主机的一个目录数据写到宿主机上容器删了数据还在。上面的MySQL例子中-v /opt/data:/var/lib/mysql就是关键。/var/lib/mysql是MySQL在容器内存储数据的位置挂载到宿主机/opt/data之后无论容器怎么折腾数据都落在宿主机目录里。数据卷挂载有两种写法-v 宿主机绝对路径:容器内路径比如-v /opt/mysql/data:/var/lib/mysql路径是固定的方便直接查看和备份。-v 卷名:容器内路径比如-v mysql_data:/var/lib/mysql由Docker管理存储位置跨主机迁移更方便但位置隐蔽操作起来没有直接映射直观。开发环境建议用绝对路径映射能看到文件心里踏实。生产环境用命名卷更规范升级迁移时依赖Docker的管理。4. 实战第一弹用Docker部署MySQL 8.0并保证数据持久化4.1 拉镜像、起容器、配置参数MySQL是日常开发中部署频率最高的数据库之一。用Docker部署MySQL比直接用系统包管理器安装省太多事——不用处理用户权限、不用管目录结构、不用纠结配置文件位置。先拉取MySQL 8.0镜像docker pull mysql:8.0然后启动容器这里给出一个开发环境足够用、数据又能持久化的完整命令docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyPssw0rd2024 \ -e TZAsia/Shanghai \ -v /opt/mysql8/conf:/etc/mysql/conf.d \ -v /opt/mysql8/data:/var/lib/mysql \ --restart always \ mysql:8.0这个命令里值得展开说的是-e TZAsia/Shanghai。不设置这个环境变量的话容器默认时区是UTCMySQL的now()函数返回的时间会比北京时间晚8个小时。很多新手上线后查数据发现时间不对排查半天最后发现是时区问题所以一开始就设置好。数据目录/opt/mysql8/data会自动创建第一次启动时会初始化MySQL的数据文件这个过程需要一些时间所以启动后别急着连接等个10秒再试。检查是否启动成功docker ps | grep mysql8如果容器状态是Up说明起来了。用客户端连接测试mysql -h127.0.0.1 -uroot -p4.2 自定义配置文件的挂载玩法实际项目中往往需要修改MySQL的配置比如调整max_connections、设置character_set_server、关闭mysqlx端口等。官方MySQL镜像的配置文件默认在/etc/mysql但直接改容器内的文件不现实——容器一删配置跟着没了。正确的做法是在宿主机上写好配置文件再挂载进去。在/opt/mysql8/conf下新建一个my.cnf写入[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci max_connections500 default-time-zone08:00 [client] default-character-setutf8mb4然后重启容器docker restart mysql8重启后MySQL就会自动加载这个配置文件。这里有个排查经验如果重启后发现配置没生效检查一下文件权限和文件格式——MySQL对配置文件权限很敏感权限太宽松会直接忽略Windows上编辑过的文件如果包含CRLF换行符也会导致解析失败需要用dos2unix转换一下。utf8mb4和utf8的区别也要说清楚。utf8在MySQL中实际上最多只支持3字节的字符而现在的Emoji表情和一些生僻字是4字节的存不进去会报错。用utf8mb4才是完整意义上的“UTF-8”。现在新项目建库默认都应该用utf8mb4。4.3 备份与恢复容器删了数据也别慌数据持久化之后备份就变成常规操作了。Docker容器里的MySQL支持用docker exec执行常规的备份命令docker exec mysql8 sh -c exec mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD /opt/backup/all_databases.sql恢复的时候把备份文件反向导进去docker exec -i mysql8 sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD /opt/backup/all_databases.sql备份文件存放在宿主机/opt/backup下配合crontab定时任务就可以实现每天自动备份。但要注意备份文件别放在容器里否则容器删了备份也跟着没了。在测试环境我踩过一个典型的坑有人图省事MySQL容器起了不带-v参数用了一个月数据库里积累了重要数据某天手欠执行了docker rm清理容器数据全部消失。这种操作失误在论坛里几乎每天都能看到新手求助。容器可以随便删但挂载在宿主机上的数据目录才是你真正的数据资产。5. 实战第二弹Docker部署Redis主从模式5.1 为什么需要主从部署Redis作为缓存数据库开发环境单节点就够用但一到生产环境单点故障就成了大问题Redis挂了全部请求直接打到数据库数据库瞬间就可能扛不住。主从复制的核心思想一个主节点负责写一个或多个从节点负责读。主节点写入数据后自动同步到从节点。主节点挂了从节点还能提供读服务配合哨兵Sentinel或者Redis Cluster从节点还能自动升级为主节点。用Docker部署Redis主从的好处是一套配置文件加几条命令就能在几分钟内搭建出拓扑结构比起手动安装配置要高效得多。5.2 用Docker Compose一次起一个主从集群Docker Compose是Docker官方的多容器编排工具用YAML格式定义多个容器的配置一条docker compose up命令就能把整个集群拉起来。先创建一个目录并进入mkdir -p /opt/redis-cluster cd /opt/redis-cluster创建docker-compose.ymlversion: 3.8 services: redis-master: image: redis:7.0 container_name: redis-master restart: always ports: - 6379:6379 command: [redis-server, --requirepass, redis123456, --appendonly, yes] volumes: - master_data:/data redis-slave1: image: redis:7.0 container_name: redis-slave1 restart: always ports: - 6380:6379 command: [redis-server, --slaveof, redis-master, 6379, --masterauth, redis123456, --requirepass, redis123456, --appendonly, yes] depends_on: - redis-master volumes: - slave1_data:/data redis-slave2: image: redis:7.0 container_name: redis-slave2 restart: always ports: - 6381:6379 command: [redis-server, --slaveof, redis-master, 6379, --masterauth, redis123456, --requirepass, redis123456, --appendonly, yes] depends_on: - redis-master volumes: - slave2_data:/data volumes: master_data: slave1_data: slave2_data:在这个配置里主节点开着密码访问和AOF持久化两个从节点通过--slaveof指定主节点并通过--masterauth配置了访问主节点的密码。后面三个volumes声明了命名卷确保Redis重启或容器重建后AOF文件不会丢。启动docker compose up -d实时查看运行状态docker compose ps5.3 验证主从同步是否正常验证方法很简单在主节点写一个key去从节点读# 进入主节点 docker exec -it redis-master redis-cli -a redis123456 127.0.0.1:6379 SET student:1001 zhangsan OK 127.0.0.1:6379 exit # 进入从节点1查看同步 docker exec -it redis-slave1 redis-cli -a redis123456 127.0.0.1:6379 GET student:1001 zhangsan能查出zhangsan说明主从同步正常。在Redis 5.0以上的版本中从节点默认是只读模式如果往从节点写数据会报错READONLY这实际上是安全特性的体现防止误操作污染从节点数据。这里想特别说一个选型问题生产环境部署Redis我个人的建议是优先使用Docker Compose而非裸机安装。裸机安装Redis需要手动管理配置文件、系统服务、日志轮转而用Compose的方式整个部署配置就在一个YAML文件里可读性和可复制性都更好。当然如果对性能有极致要求比如单实例QPS在某百万级别以上裸机部署确实还有性能优势毕竟容器本身有极小的网络和IO开销。常规业务场景这个差异基本感知不到。6. Docker进阶必备Compose与镜像构建6.1 Docker Compose为什么是生产环境利器Compose除了用来起Redis集群更常见的场景是编排一套完整的应用栈。比如你有一个Web应用依赖MySQL、Redis三个服务用一个docker-compose.yml管理一条命令全部启动按需伸缩这在本地开发和测试环境非常好用。服务之间的通信Compose内部自动创建了一个网络服务名就是互相访问的主机名。比如上面的YAML里从节点用redis-master这个主机名去连接主节点而不是用IP。这个设计很关键如果主节点IP变了服务名不变配置就不用改。在/opt/redis-cluster目录下执行docker compose down会把所有容器和默认网络删除但数据卷默认保留。docker compose down -v则会连数据卷一起删这个参数在生产环境操作时要非常谨慎——一旦执行Redis里的数据全部消失。如果你需要先写一个项目再打包成镜像docker build是绕不开的。下面拿Java Spring Boot应用举个例子假设已经通过IDE打包出了JAR包。在项目根目录创建DockerfileFROM openjdk:17-jdk-slim LABEL maintaineryournameexample.com WORKDIR /app COPY target/myapp.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]然后在项目根目录执行docker build -t myapp:1.0.0 .构建完查看镜像运行容器docker run -d --name myapp -p 8080:8080 myapp:1.0.0这套流程在开发环境可以配合CI/CD工具实现自动化开发提交代码流水线自动打包构建镜像、推送仓库服务器自动拉取新镜像并滚动更新整套流程就是微服务和云原生的基础。这里有个新手容易忽视的细节-t标签中的版本号最好和代码发布版本保持一致而不是用latest。生产环境部署时如果用latest哪天服务器拉镜像拉到一个不兼容的新版本整个服务可能直接不可用。养成给镜像打明确版本号的习惯回滚也方便。6.3 私有镜像仓库团队的镜像管理中心团队开发时镜像不适合直接推到公网的Docker Hub这时候需要在内部搭建私有镜像仓库。Docker官方的registry镜像可以快速拉起一个docker run -d \ --name registry \ -p 5000:5000 \ -v /opt/registry:/var/lib/registry \ --restart always \ registry:2打完本地镜像后打个标签推到私有仓库# node是宿主机IP构建机器的/etc/docker/daemon.json里需要配置insecure-registries: [node:5000] docker tag myapp:1.0.0 node:5000/myapp:1.0.0 docker push node:5000/myapp:1.0.0这个方案能解决大部分团队内部的需求。如果要求更高的权限管理和镜像清理能力可以换用Harbor它功能更完整——支持Web管理界面、项目隔离、镜像签名和漏洞扫描。私有仓库加上镜像清理策略基本就是中小团队标准配置了。7. 常见问题排查与避坑指南7.1 Docker Desktop起不来先排查这四件事虚拟化未开启这是Windows用户的第一道门槛前面已经详细说过了进BIOS开启虚拟机化选项或者启用Windows虚拟机平台和WSL2。WSL2版本太旧运行wsl --update升级到最新内核。防火墙拦截Docker Desktop提示网络相关错误时检查Windows防火墙有没有放行Docker需要的端口。资源占用过高Docker Desktop默认会用宿主机一部分CPU和内存可以到Settings里限制资源占用给其他应用留出空间。7.2 容器启动失败如何用日志定位问题容器启动失败时第一步永远是看日志docker logs 容器名日志会直接告诉你失败原因比如端口被占用了、配置文件的路径不对、环境变量缺失。定位到问题后改配置、删除容器重新跑这个循环用熟了大部分启动问题都能自己解决。日志查不到问题时可以进一步查看容器状态码docker inspect 容器名 --format{{.State.ExitCode}}退出码为0表示正常退出非0就说明启动阶段出错了。常见错误码比如1通常是配置错误或者依赖不对137一般是OOM被系统杀掉了139是段错误多半是镜像内核不兼容——这个在龙芯这类非x86架构的服务器上偶尔会遇到换镜像版本或者重新构建就能解决。7.3 镜像下载慢从源到代理的完整思路前面说过配置镜像加速器具体操作再总结一次。Linux上编辑/etc/docker/daemon.jsonWindows上操作Docker Desktop的Docker Engine配置区块写入后重启服务。有时候加速器也会不稳定可以准备两三个备用地址写成一个列表{ registry-mirrors: [ https://加速器地址1, https://加速器地址2 ] }Docker会按顺序尝试拉取成功率会高很多。7.4 容器内连接宿主机的数据库容器是独立网络空间容器内用localhost或127.0.0.1访问的是容器自己不是宿主机。容器内要连接宿主机的MySQL或其他服务可以用host.docker.internal这个特殊域名Windows和Mac平台的Docker Desktop默认支持。Linux上如果这个域名不可用可以在docker run时加参数--add-hosthost.docker.internal:host-gateway把host.docker.internal解析到宿主机。这个坑在开发场景中非常常见——比如你的应用放在容器里但MySQL直接装在宿主机上容器里配置的数据库连接地址如果写localhost一定会报连接拒绝因为容器里根本没有MySQL。8. 从入门到落地几点实际经验分享Docker的命令和概念其实不难一两天就能上手。真正需要时间积累的是“容器思维”——部署应用时不要总想着“进到机器里安装配置”而是想“如何用镜像描述出这套环境”这才是Docker最大的价值所在。对整个入门过程我自己的体会是先跑通一个容器再研究原理和最佳实践。不要一开始就陷入Dockerfile写法的各种细节把MySQL、Redis这么跑起来用数据卷解决持久化用Compose解决编排日常开发就基本够用了。等到要上生产环境再回头深入镜像优化、网络模型、安全加固效率会高很多。最后分享一个实用的小工具docker ps --format。默认的docker ps输出列很多终端小的时候看着很乱可以用自定义格式精简docker ps --format table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}把它做成shell别名dps以后查容器状态会清爽很多。这个技巧虽然只有一个命令但每天都会用到实操体验提升明显。