Docker从入门到实战:核心概念、安装部署与高频报错排查
2026/9/16 2:47:38 网站建设 项目流程

第一次接触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 wasn't detected,这基本都是BIOS里虚拟化开关没打开。重启电脑进BIOS界面(一般是开机时按Del或者F2,不同主板不一样),找到“Intel Virtualization Technology”或“SVM Mode”这类选项,设置为Enabled,保存退出重新进系统。

第三,安装并启用WSL2。在管理员权限的PowerShell里依次执行下面两条命令,然后重启电脑:

wsl --install wsl --set-default-version 2

virtualization 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 Linux(Ubuntu/CentOS)安装Docker

Linux安装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 docker

2.3 Docker Desktop启动失败排查

Windows下装了Docker Desktop却启动不了,是新手群里出现频率最高的求助。除了前面说的虚拟化没开,还有几个常见原因值得单独拎出来说。

一是旧版本残留。如果以前装过Docker Toolbox或者老版本Docker Desktop,卸载不干净会导致新版本启动失败。建议卸载后手动检查目录C:\Program Files\DockerC:\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_PASSWORD=123456 \ mysql:8.0

这条命令看起来不长,但每个参数都值得认真理解,面试也经常问。-d表示后台运行容器,不会占用当前终端。--name mysql8给容器起一个名字,后续操作这个容器不需要再记一串随机ID。-p 3306:3306做端口映射,宿主机冒号前的3306对应容器内部的3306,意思是访问本机3306端口就相当于访问容器的3306端口。容器的网络默认是隔离的,不映射端口宿主机根本访问不到容器里的服务。-e MYSQL_ROOT_PASSWORD=123456是设置环境变量,这里指定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_PASSWORD=123456 \ -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-server=utf8mb4 collation-server=utf8mb4_unicode_ci [client] default-character-set=utf8mb4

保存后执行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.yml

version: '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自动创建的内部DNS,Compose内部网络中服务名即可互相解析,不需要写死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_slavesmaster_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端口映射到宿主机的8080;SSH协议对应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完成。

在项目根目录创建Dockerfile

FROM openjdk:8-jdk-alpine LABEL maintainer="yourname@example.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_SERVER=http://eureka-server:8761/eureka/ business-service: image: registry.example.com/cloud/business-service:1.0.0 depends_on: - eureka-server - mysql8 environment: - EUREKA_SERVER=http://eureka-server:8761/eureka/ - DB_HOST=mysql8 - DB_PASSWORD=123456

每个服务的配置都包含镜像地址、端口映射、依赖关系和环境变量。注意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 unknownnot 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.jsoninsecure-registries字段中,这样HTTP协议也能拉取,适合内网环境。

我在实际使用中还有一个体会:排查Docker问题要保持思路清晰,先分清楚是镜像问题、容器问题,还是网络问题。对应地先看docker images确认镜像在不在,再看docker ps -a看容器状态,最后docker logs看容器日志,三个命令用下来,大部分问题都能定位到原因,不用瞎猜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询