第一次接触Docker的人通常会被那一堆镜像、容器、仓库的概念绕晕。我自己当年也是从Windows上装Docker Desktop翻车开始一路踩坑踩过来的。等到真的把MySQL、Redis、GitLab这些常用服务一个个用容器跑起来之后才真正体会到“一次构建到处运行”到底意味着什么。这篇Docker学习教程我不会讲太多高深的理论重点放在“怎么装、怎么用、怎么排查问题”上经典的服务部署实战也会完整走一遍。适合刚接触Docker的开发者、准备用容器优化本地开发环境的同学以及想把项目快速交付出去但不想在生产环境折腾依赖的运维朋友。要说清楚Docker这个工具我认为最靠谱的方式是直接上手。反正镜像拉坏了可以删容器跑挂了可以重建试错成本几乎为零。下面的内容我会按照一个完整的入门路径来安排先从基础概念建立整体认知再把Windows和Linux环境下的安装讲透接着用MySQL、Redis主从这种高频需求做实战最后补上GitLab、IDEA打包镜像、微服务部署这类进阶场景以及我遇到过的各种奇葩报错。1. 先搞懂这几个核心概念再动手1.1 Docker到底解决什么问题在讲操作之前先解决一个根本问题我们为什么需要Docker我见过不少朋友在本地开发时一切正常代码提交到服务器以后就是跑不起来。查来查去最后发现是服务器上的MySQL版本不对、Redis编译参数缺了某个插件、Node.js版本差了一个大版本这种问题统称为“环境地狱”。Docker的思路是把应用和它依赖的运行环境一起打包成一个标准单元这个单元就叫镜像。你在自己的电脑上把这个镜像跑起来它是一个容器放到服务器上跑起来它也还是那个容器。因为镜像里已经包含了操作系统层、运行库、项目代码以及所有配置所以换一台机器部署的时候不再需要重新装东装西拉下来就能启动。这就是容器化最核心的价值。拿生活中举例子虚拟机像是一个人整租了一套房子厨卫、卧室、客厅全都是隔离的体积大且启动慢。容器更像是住在公寓里大家共用同一个操作系统内核也就是物业管理处但每个房间里的家具和装修都是自己的互相看不见也互相不影响。正因为共享了宿主机内核容器镜像比虚拟机小很多启动速度也快得多基本是秒级。1.2 镜像、容器、仓库一个生活化类比讲清楚Docker有三个高频出现的词镜像、容器、仓库。很多新手在这儿会卡住我用个类比帮忙理清。镜像Image可以理解为“安装光盘”或者“模板”。它是只读的定义了这个环境里有什么软件、什么配置、跑什么命令。你用同一个镜像可以创建出很多个相同的容器就像用同一张系统盘可以装出很多台电脑。容器Container则是镜像运行起来之后的实例可以理解为“已经装好系统的电脑”。容器可以被启动、停止、删除你可以在容器里执行命令、修改文件容器的状态和镜像本身是隔离的。哪怕你把容器里的数据改得乱七八糟删掉这个容器再用原来的镜像重新创建一个又是干干净净的。仓库Repository是集中存放镜像的地方类比成“应用商店”或者“网盘”。最常用的公开仓库是Docker Hub你在里面可以找到官方维护的MySQL、Redis、Nginx等镜像也可以上传自己打包好的镜像供别人下载使用。后面要讲的“配置镜像加速源”本质上就是让你从国内更快的仓库地址拉取镜像。这三个概念搞明白之后你再看任何Docker命令都会顺畅很多docker pull是把镜像从仓库拉到本地docker run是用镜像启动一个容器docker build是制作一个新的镜像docker push是把本地镜像推送到仓库。2. 环境准备Windows和Linux安装Docker的完整流程2.1 Windows上安装Docker Desktop的前置条件Windows安装Docker最常规的方式是使用Docker Desktop但它有几个前置条件很多人就是倒在这一步。第一系统版本。Docker Desktop要求Windows 10 64位专业版、企业版、教育版或Windows 11家庭版理论上也能装但需要手动启用Hyper-V和WSL2操作稍微绕一些。如果你电脑是Win11家庭版基本没有太多障碍正常安装即可。第二CPU虚拟化必须在BIOS里开启。安装完成后启动Docker Desktop如果弹窗提示virtualization support not detected或者Docker Desktop failed to start because virtualisation support wasnt detected这基本都是BIOS里虚拟化开关没打开。重启电脑进BIOS界面一般是开机时按Del或者F2不同主板不一样找到“Intel Virtualization Technology”或“SVM Mode”这类选项设置为Enabled保存退出重新进系统。第三安装并启用WSL2。在管理员权限的PowerShell里依次执行下面两条命令然后重启电脑wsl --install wsl --set-default-version 2virtualization support not detected这类报错排查完BIOS和WSL2之后大概率能解决。如果还不行再检查一下Windows功能里有没有开启“适用于Linux的Windows子系统”和“虚拟机平台”这两项在控制面板的“启用或关闭Windows功能”里勾选上即可。安装完成后建议在命令行执行docker version确认客户端和服务端都返回了版本信息。如果只输出了客户端信息而服务端连接失败通常说明Docker Desktop引擎没有起来。Windows下还会遇到failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxenvironment这类报错基本都是Docker Desktop还没启动完成或者启动过程中崩了等几秒重试或者右下角托盘图标右键选择Restart。2.2 LinuxUbuntu/CentOS安装DockerLinux安装Docker相对简单但不同发行版命令差异比较大实际工作中Ubuntu和CentOS两个派系最常碰到。Ubuntu系统我建议用官方脚本安装简单粗暴curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本会把Docker引擎、命令行工具、containerd运行时以及Compose插件一并装好。安装完成之后执行sudo systemctl enable docker sudo systemctl start docker把Docker设置为开机自启。CentOS 7上如果系统自带的Docker版本太老需要先升级。推荐手动配置Docker官方CentOS仓库再安装sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io -y sudo systemctl start docker sudo systemctl enable docker这里特别提醒CentOS 7的内核版本问题。Docker对内核版本有要求建议先执行uname -r看一下如果内核低于3.10后续容器运行容易出各种玄学问题建议先升级内核再装Docker。Linux环境还有一个高频问题普通用户直接执行docker ps会提示权限不足。正确做法是把当前用户加入docker用户组重新登录终端后就不用每次加sudo了。sudo usermod -aG docker $USER newgrp docker2.3 Docker Desktop启动失败排查Windows下装了Docker Desktop却启动不了是新手群里出现频率最高的求助。除了前面说的虚拟化没开还有几个常见原因值得单独拎出来说。一是旧版本残留。如果以前装过Docker Toolbox或者老版本Docker Desktop卸载不干净会导致新版本启动失败。建议卸载后手动检查目录C:\Program Files\Docker和C:\Users\你的用户名\AppData\Local\Docker是否还存在有的话直接删掉再重装。二是内存资源不足。Docker Desktop默认需要分配2GB以上的内存给WSL2虚拟机如果电脑内存本身吃紧或者有大型软件占用了大量资源引擎就会反复启动失败。可以在Docker Desktop的Settings - Resources里调整内存大小或者在任务管理器里看看有没有进程把内存占满了。三是Hyper-V与第三方虚拟化软件冲突。比如电脑上装了VMware Workstation或者VirtualBox它们和Docker Desktop的Hyper-V不能同时运行。这个属于老生常谈的问题解决办法是在使用Docker期间关闭第三方虚拟化软件或者切换到WSL2后端模式避免冲突。3. 镜像管理拉镜像慢、无法连接仓库的解决办法3.1 配置镜像加速源docker pull拉取镜像特别慢甚至卡住不动是让无数人抓狂的问题。默认情况下Docker从Docker Hub拉取镜像而Docker Hub服务器在国外网络延迟高、不稳定。解决方案是配置国内可用的镜像加速源。Windows用户在Docker Desktop的Settings - Docker Engine里修改JSON配置在registry-mirrors字段里填入加速地址。Linux用户则修改/etc/docker/daemon.json没有这个文件就新建一个{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }改完重启Docker服务sudo systemctl daemon-reload sudo systemctl restart docker配置完加速源以后再拉镜像速度会有质的提升。顺带提一句不同时期可用的加速源变动很频繁如果发现某个地址失效了可以换一个试试但是不建议随意填写来路不明的加速地址有数据安全和供应链安全的风险。3.2 高频镜像操作命令速查我平时用得最多的镜像相关命令就下面这些整理成速查表可以直接抄作业。操作命令搜索镜像docker search nginx拉取镜像docker pull nginx:latest查看本地镜像docker images删除镜像docker rmi nginx:latest导出镜像docker save -o nginx.tar nginx:latest导入镜像docker load -i nginx.tar查看镜像历史docker history nginx:latest这里说两个容易踩坑的点。第一删除镜像前要把使用该镜像的容器先删掉否则会报“image is being used by container”错误。第二docker rmi后面跟的可以是镜像名加标签也可以是镜像ID但用镜像名时如果不加标签默认删的是latest标签对应的镜像。还需要了解一个概念叫“镜像分层”。Docker镜像不是一个大文件而是由多层只读文件系统叠加而成的。拉镜像时你会看到类似Downloaded newer image for nginx:latest的信息其实就是逐层拉取。这也是为什么Docker能省磁盘空间——多个镜像共享的层只需要存一份。但如果你用docker save导出镜像一定要知道它把所有层打成了一个tar包体积往往是各层加起来的总和。4. 实战一用Docker安装MySQL 8.0并连接到客户端4.1 拉取镜像和启动容器的参数解析学习Docker最好的方式是用它跑一个真实的服务。MySQL 8.0是绝大多数项目的标配我就拿它当第一个实战案例。启动MySQL 8.0的完整命令如下docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ mysql:8.0这条命令看起来不长但每个参数都值得认真理解面试也经常问。-d表示后台运行容器不会占用当前终端。--name mysql8给容器起一个名字后续操作这个容器不需要再记一串随机ID。-p 3306:3306做端口映射宿主机冒号前的3306对应容器内部的3306意思是访问本机3306端口就相当于访问容器的3306端口。容器的网络默认是隔离的不映射端口宿主机根本访问不到容器里的服务。-e MYSQL_ROOT_PASSWORD123456是设置环境变量这里指定MySQL的root密码实际使用中请换成强度足够的密码。执行完命令后运行docker ps看到状态为Up就说明容器正常启动了。可以在宿主机用docker exec -it mysql8 mysql -uroot -p123456进入MySQL命令行交互界面验证是否真的能连上。4.2 数据持久化与配置文件挂载直接用上面的命令跑MySQL有一个致命问题容器一旦被删除里面所有的数据库数据都会消失因为数据默认写在容器的可写层里。容器重建后一切归零这在生产环境是完全不可接受的。解决方案是使用数据卷或挂载目录。MySQL容器官方支持把宿主机目录挂载到容器内的/var/lib/mysql实现数据持久化docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v /opt/mysql-data:/var/lib/mysql \ -v /opt/mysql-config:/etc/mysql/conf.d \ mysql:8.0-v参数的作用是把宿主机的/opt/mysql-data目录与容器内的/var/lib/mysql目录关联起来。容器里写入的数据会实时同步到宿主机这个目录哪怕容器被删掉重建只要挂载同一个宿主机目录数据就还在。这就是“数据与容器生命周期解耦”的思想。第二个-v挂载配置目录也很重要。MySQL 8.0默认字符集需要手动确认否则可能出现中文乱码。在宿主机/opt/mysql-config下创建my.cnf文件[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci [client] default-character-setutf8mb4保存后执行docker restart mysql8重启容器让配置生效。4.3 远程连接报错的典型处理MySQL容器跑起来以后用Navicat或DBeaver连接不上是常见后续烦恼。首先要排查端口。容器确实在跑但3306端口有没有被宿主机防火墙拦截云服务器需要检查安全组规则是否放行了3306端口。本地调试的话可以先执行telnet 127.0.0.1 3306测试通不通。其次是MySQL 8.0的认证插件问题。MySQL 8.0默认的密码认证插件是caching_sha2_password而一些老版本的数据库客户端不兼容会报Authentication plugin caching_sha2_password cannot be loaded。解决办法有两个方向一是升级客户端到支持该插件的版本二是在容器里修改root用户的认证插件但不建议为了迁就旧客户端降低安全性。还有一个经常被忽略的问题是容器里MySQL的bind-address设置。默认情况下MySQL只监听本机连接容器内外网连接需要确认没有限制。进入容器执行docker exec -it mysql8 mysql -uroot -p123456 -e select user, host from mysql.user;查看root用户的host是不是%。如果不是执行SQL把host改成%再刷新权限ALTER USER root% IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;这句话的意思是允许任何主机通过root账号连接并且使用兼容性更好的mysql_native_password插件。实际操作中建议为远程连接单独创建专用账号不要给root开远程权限。5. 实战二Docker Compose部署Redis主从5.1 编写docker-compose.yml单容器用docker run就够了但真实项目往往是多个容器协同工作。比如Redis主从复制至少需要两个节点如果用两条docker run命令去管理既难维护也不好扩展。这时候就该用Docker Compose。Docker Compose通过一个YAML文件定义整个服务编排一条命令完成启动、停止、查看日志等操作。创建一个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, --appendonly, yes] redis-slave: image: redis:7.0 container_name: redis-slave restart: always depends_on: - redis-master ports: - 6380:6379 command: [redis-server, --slaveof, redis-master, 6379]这个配置的核心是定义了两个服务redis-master是主节点redis-slave是从节点。image指定镜像container_name指定容器名ports做端口映射command覆盖默认启动命令。关键在于command里的参数。主节点开启AOF持久化--appendonly yes避免重启丢数据。从节点通过--slaveof redis-master 6379指定主节点的地址和端口这里redis-master不是IP而是Compose自动创建的内部DNSCompose内部网络中服务名即可互相解析不需要写死IP。depends_on表示从节点依赖主节点先启动。需要注意这个依赖只是控制启动顺序不会等主节点完全就绪如果主节点启动慢从节点可能会先启动失败不过Redis主从复制失败后会自动重试问题不大。5.2 启动、验证主从同步在docker-compose.yml所在目录下执行docker compose up -d-d同样是后台运行。执行docker compose ps可以查看两个容器的运行状态。全部显示Up之后验证主从是否正常同步。进入主节点写入数据docker exec -it redis-master redis-cli set name hello-redis进入从节点查看数据docker exec -it redis-slave redis-cli get name如果返回hello-redis说明主从同步成功。还可以用docker exec -it redis-slave redis-cli info replication查看复制的详细状态重点看connected_slaves和master_link_status两个字段master_link_status:up表示连接正常。Redis主从架构虽然简单但生产环境还要考虑几个问题。一是主节点有密码时从节点配置需要额外加--masterauth参数传递主节点密码。二是主从复制不等于高可用主节点宕机后从节点不会自动升级要做到自动故障转移还需要引入哨兵模式这是更高阶的玩法。三是docker-compose.yml文件本身要纳入版本管理因为它就是整个服务拓扑的“基础设施即代码”。Docker Compose的日志管理也值得说一句。docker compose logs -f redis-master可以实时查看指定服务日志排错很重要。如果某个容器启动失败用这个命令基本能定位到原因。6. 进阶玩法GitLab、IDEA打包、微服务部署6.1 用Docker搭建GitLab代码仓库很多团队想自己搭一个代码托管平台GitLab是最常见的选择。传统安装方式需要配置Ruby、PostgreSQL、Redis等一堆依赖过程相当痛苦。用Docker部署就舒服多了docker run -d \ --name gitlab \ --restart always \ -p 8443:443 \ -p 8080:80 \ -p 2222:22 \ -v /opt/gitlab/config:/etc/gitlab \ -v /opt/gitlab/logs:/var/log/gitlab \ -v /opt/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest端口映射这里要注意宿主机80端口如果被其他服务占了可以把容器内80端口映射到宿主机的8080SSH协议对应22端口如果宿主机22端口被系统SSH占用也建议映射成2222。容器启动后第一次初始化需要几分钟到十几分钟不等通过docker logs -f gitlab可以看启动进度。初始化完成后浏览器访问http://服务器IP:8080首次进入会要求设置root密码。GitLab镜像体积大、吃内存官方建议宿主机至少4GB内存可用否则容器很容易启动失败或者运行卡顿。低配服务器上部署GitLab建议准备swap空间或者考虑用Gitea这类更轻量的方案。6.2 IDEA一键打包Docker镜像的思路Java后端同学经常需要在IDEA里把Spring Boot项目打成Docker镜像并部署到服务器。比较主流的方式是使用IDEA的Docker插件配合Dockerfile完成。在项目根目录创建DockerfileFROM openjdk:8-jdk-alpine LABEL maintaineryournameexample.com WORKDIR /app COPY target/demo-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]然后先在IDEA的Maven面板里执行package打包出jar文件再配置Docker部署。IDEA的Docker插件通过TCP协议连接服务器的Docker守护进程服务器端需要修改Docker配置打开远程访问端口并且配置TLS证书保证安全这个操作有一定风险在自己学习环境里折腾没问题公司生产环境不建议开放Docker远程端口。关于打包这里有个细节Dockerfile里COPY的路径是相对于构建上下文的如果构建上下文配置不对经常报COPY failed: no source files。在IDEA的Dockerfile右键选择Run on Docker时注意把build context指定为项目根目录让IDEA能正确找到target目录下的jar包。6.3 微服务项目用Docker Compose统一编排微服务架构很典型的场景是一个项目拆成多个服务每个服务一个镜像加上网关、注册中心、配置中心、消息队列、数据库大大小小十几个容器。这时候一条条去docker run根本就不现实Docker Compose就是微服务编排的入门工具。写一个简单的微服务编排示例version: 3.8 services: eureka-server: image: registry.example.com/cloud/eureka-server:1.0.0 ports: - 8761:8761 gateway-service: image: registry.example.com/cloud/gateway-service:1.0.0 ports: - 8080:8080 depends_on: - eureka-server environment: - EUREKA_SERVERhttp://eureka-server:8761/eureka/ business-service: image: registry.example.com/cloud/business-service:1.0.0 depends_on: - eureka-server - mysql8 environment: - EUREKA_SERVERhttp://eureka-server:8761/eureka/ - DB_HOSTmysql8 - DB_PASSWORD123456每个服务的配置都包含镜像地址、端口映射、依赖关系和环境变量。注意Spring Boot服务之间通过服务名互相调用比如网关转发到业务服务用的是http://business-service:8080而不是IP。这种服务发现机制保证了容器IP变化后服务间通信不受影响。这种部署方式还有很多可以展开的话题比如配置中心、链路追踪、监控告警但作为入门教程点到为止。建议你先用Compose把本地的多个服务编排跑起来体会一下“一键启动整个项目”的流畅感再逐步接触K8s这种大规模容器编排平台会发现很多概念其实是相通的。7. 高频报错与排查实录7.1 权限问题的处理Docker权限错误出现的频率极高尤其是Linux下刚装完Docker的新手。最常见的一条Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock原因很好理解Docker守护进程的socket文件默认属于root用户和docker用户组普通用户不在这个组里就没权限访问。解决办法在2.2节已经提过执行sudo usermod -aG docker $USER然后退出当前终端重新登录或者执行newgrp docker刷新当前会话的用户组。如果你是在脚本里或者CI环境中遇到这个错误先确认执行用户是否真的已经加入了docker组很多人加完组忘了重新登录导致一直报错。7.2 服务启动失败常见原因容器启动失败时第一步永远是看日志docker logs 容器名。不过有些错误不看日志也知道大概率是怎么回事。docker: unexpected EOF这个报错在拉镜像或者启动容器时都可能出现。它本质上是一个网络层面的异常意味着与服务端的连接被中断了。排查思路依次是检查网络连接是否稳定检查磁盘空间是否充足镜像解压到本地过程磁盘满了也会报EOF最后考虑重新拉取镜像因为本地缓存的镜像层可能损坏了。docker run能执行但容器状态总是Exited这种一般是镜像里的应用启动后自动退出了。比如指定的启动命令执行完退出、配置文件写错导致服务启动失败、容器内存分配不足被OOM杀掉。先用docker logs看启动过程的输出再考虑是不是内存问题用docker stats观察宿主机内存占用。failed to connect to the docker api这个报错Windows和Linux都有含义是Docker命令行客户端连不上dockerd守护进程。Windows下通常是Docker Desktop没启动Linux下通常是dockerd服务挂了。Windows检查右小角鲸鱼图标状态Linux执行sudo systemctl status docker看服务状态必要时sudo systemctl restart docker。7.3 拉取镜像时的网络与存储报错拉镜像最怕两件事慢和失败。慢的问题在3.1节讲了配置加速源。失败的话常见报错有这么几类。manifest unknown或not found说明你要拉取的镜像标签不存在。可能是版本号写错了比如mysql:8.0.30写成mysql:8.0.3也可能是这个镜像压根没有你要的标签先去Docker Hub确认一下准确版本号。no space left on device就简单了磁盘满了。清理Docker的悬空资源用一条命令docker system prune这条命令会清理所有已停止的容器、未被使用的网络、悬空镜像以及构建缓存执行前会提示你确认。如果想保留数据卷加-volumes参数前先想清楚数据卷里是有持久化数据的别误删了。还有一个容易被忽略的坑是docker login失效。如果你从私有仓库拉取镜像时突然出现unauthorized: authentication required很可能token过期了重新docker login一下即可。公司内部的私有仓库地址一般配置在/etc/docker/daemon.json的insecure-registries字段中这样HTTP协议也能拉取适合内网环境。我在实际使用中还有一个体会排查Docker问题要保持思路清晰先分清楚是镜像问题、容器问题还是网络问题。对应地先看docker images确认镜像在不在再看docker ps -a看容器状态最后docker logs看容器日志三个命令用下来大部分问题都能定位到原因不用瞎猜。