1. 为什么我建议你把Docker当作“便携环境箱”
早些年我在某台服务器上部署一个稍微复杂点的服务,Python、Node、Redis、MySQL全装在一台机器里。每个项目都要手工配环境变量,改依赖版本,load模块,稍不注意两个项目之间的包就互相踩脚。后来了一个项目需要升级某个库,结果把另一个项目搞挂了,恢复环境花了整整一晚上。从那天开始,我对Docker的态度就从“听说过”变成“必须用”。
Docker最核心的价值可以理解成“便携环境箱”。不管你在笔记本、云主机还是客户内网机,只要装了Docker,一个镜像打包过去,立刻就能复现出完全一致的环境。它解决的最大痛点是环境一致性问题:开发机上跑得好好的,到了测试服务器就各种报错;或者一套环境只能在某个机器上跑,换台机器就得重新折腾。Docker把整个运行环境(系统库、运行时、配置、依赖)全部打包进镜像,你在哪里跑,得到的结果都一样。
适合看这篇教程的人,主要是刚开始接触Linux服务器、想用Docker统一管理应用的开发者,以及被环境问题折磨过的新手运维。学完这一篇,你至少能完成从“安装Docker”到“跑起来第一个容器”的全过程,同时对镜像、容器、数据卷这些核心概念建立初步认识。
我自己的体验是,Docker的学习曲线其实并不陡,真正卡住新人的不是命令记不住,而是脑子里没有把“镜像”和“容器”这两个概念区分开。只要把这个模型想明白,后面的操作基本是水到渠成的事。所以这篇教程我不会只堆命令,还会把每个命令背后的逻辑讲清楚,这样才能举一反三。
2. CentOS下安装Docker的完整过程
这一节先解决环境准备,别急着敲docker run。CentOS系统默认源里有一个老旧的docker包,但功能不全,不能直接用来跑现代镜像。我们装的是docker-ce(社区版),这是当前最主流的发行包。
2.1 系统要求与内核检查
开始安装前,先确认系统版本和内核。Docker对CentOS 7的支持已经非常成熟,CentOS 8也能用,但不同小版本的软件源配置略有差异,这里我以CentOS 7为主讲,CentOS 8的用户只需把命令中的版本号调整一下。
先看版本:
cat /etc/redhat-release uname -r内核建议在3.10以上,CentOS 7默认内核就能满足。再看selinux状态,一般保持enforcing也没问题,但初学者如果遇到权限问题,建议先临时调整或使用挂载时加:z后缀绕过,后面会说。
2.2 卸载系统自带的旧docker包
即使你还在用旧版本,也建议先清理掉,避免干扰。执行:
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine如果提示没有这些包,忽略即可,这一步只是保险。
2.3 配置yum软件源并安装Docker
我习惯把yum-utils、device-mapper-persistent-data和lvm2先装上,前两个用于配置仓库,后者用于Docker的存储驱动。
sudo yum install -y yum-utils device-mapper-persistent-data lvm2然后配置Docker官方源。如果你在国外的VPS,直接用官方源;如果在内网环境,建议把URL里的download.docker.com换成可访问的镜像地址(每家机房都有自己的镜像站点,找服务商要即可)。我这里以官方站点的写法为例:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo接下来直接安装:
sudo yum install -y docker-ce docker-ce-cli containerd.io这一步可能需要一点时间,因为依赖比较多。安装完成后,启动Docker并设置开机自启:
sudo systemctl start docker sudo systemctl enable docker验证一下版本:
docker version能看到client和server两部分版本号,就说明安装成功了。如果只看到client,说明守护进程没起来,去查systemctl status docker。
2.4 配置镜像加速:别让拉取速度毁了心情
默认的Docker Hub在国内的访问速度往往不理想,第一次拉大镜像时会非常痛苦。解决办法是配置镜像加速器。编辑/etc/docker/daemon.json,内容类似于:
{ "registry-mirrors": ["https://你的加速地址"] }加速地址填什么,要看你当前网络环境实际能不能访问。我不推荐直接抄某一个固定地址,因为这类服务经常变动。最简单的方法是在你的云厂商或机房的控制台里找“容器镜像服务”提供的专属加速地址,它和你账号绑定,可靠性更高。改完配置记得重启Docker:
sudo systemctl daemon-reload sudo systemctl restart docker然后跑一个测试容器,验证整个链路是否正常:
docker run hello-world看到一段“Hello from Docker!”的提示,说明镜像拉取、容器创建、运行整个流程都已打通。
3. 镜像和容器还没搞懂?这两种对象决定你的使用姿势
很多新手在入门阶段就卡在“镜像(image)”和“容器(container)”的区别上。用生活例子说:你可以把镜像看成做菜的菜谱,容器是按照菜谱做出来的一盘菜。菜谱本身不能吃,但你可以从一份菜谱做无数盘菜;每盘菜都可以独立添加调料或切片,或处理完直接倒掉,菜谱还在那里。
从技术的角度看,镜像是一个只读的模板,它由一层层的文件系统构成。每一层对应Dockerfile中的一条指令,比如安装依赖、拷贝代码。多个镜像可以共享底层相同的层,所以拉取新镜像时,如果已有相同层,就不用重复下载。容器则是镜像的一个运行实例:它在镜像的只读层之上加了一个可写层,程序运行过程中产生的文件都写在这一层。当容器被删除时,这个可写层也跟着消失。
这个特性解释了为什么“在容器里改了文件,容器一删就全没了”。它不是bug,而是设计。理解了这一点,你才知道为什么要用数据卷(下一节会讲)。
3.1 镜像操作的高频命令
先掌握四组命令:拉取、查看、删除、打标签。
docker pull 镜像名:标签 # 从仓库拉镜像,默认不写标签为latest docker images # 列出本地镜像 docker rmi 镜像ID或镜像名:标签 # 删除镜像 docker tag 旧镜像名:标签 新镜像名:标签 # 给镜像打标签标签(tag)不是版本号的意思,它更像一个引用。比如nginx:1.25和nginx:latest,可能指向同一个镜像,也可能不是。你在生产环境最好不用latest,而是锁定具体版本,这样再拉取时不会被动升级。
查看镜像详细信息的命令是docker inspect 镜像名,它会输出一个大型JSON。新手看这个容易头晕,一般只需要关注Config、Architecture等几个字段,用docker inspect --format可以只提取想要的部分。
3.2 容器操作的核心闭环
容器操作的闭环是:创建、启动、查看、停止、删除。
docker run <镜像> # 创建并启动容器 docker ps # 只看正在运行的容器 docker ps -a # 看所有容器,包括已退出 docker start <容器ID或名称> # 启动一个已存在的容器 docker stop <容器ID或名称> # 停止容器 docker rm <容器ID或名称> # 删除容器docker run和docker start的区别是初学者最容易混淆的。run是基于镜像创建一个新容器,start是启动一个已经创建过、现在处于停止状态的容器。每次run都会生成一个新的容器ID,旧容器如果没删,会一直堆在系统里。
再配合三个常用辅助命令:
docker exec -it <容器> bash # 进入容器内部执行命令 docker logs <容器> # 查看容器日志 docker inspect <容器> # 查看容器的所有信息日志是排查问题的第一入口。很多容器启动后立刻退出,你第一件事不是猜,而是docker logs看最后的错误输出。exec -it里的-i表示保持标准输入打开,-t分配一个伪终端,缺了它bash会不听话。这条命令组合基本是固定搭配。
4. 跑通第一个Nginx容器:从拉取到端口映射
理论知识再丰富,不上手跑一个服务总觉得虚。这一节我们用Nginx容器完整走一遍,把端口映射、运行时参数和进入容器操作全部串起来。
4.1 拉取Nginx镜像
我先采用一个标签固定的版本,避免latest带来的不确定性:
docker pull nginx:1.25等待拉取完成后,确认一下镜像:
docker images | grep nginx看到1.25标签的记录,镜像就准备好了。
4.2 运行容器并做端口映射
Nginx默认监听容器内部的80端口,如果不在运行命令时映射,宿主机无法访问到它。我用-p参数把宿主机的8080端口映射到容器的80端口:
docker run -d --name web-test -p 8080:80 nginx:1.25解释一下每个参数:-d代表后台运行,--name给容器起一个容易记住的名字,-p 宿主机端口:容器内端口做端口映射。为什么要指定名字?因为后续执行docker stop web-test比docker stop 一串随机字符舒服得多。
端口映射的理解可以打个比方:容器是一个独立的小房子,房子里的Nginx服务自己在80端口提供服务,但房子外面的人并不知道"80"这个门牌号。-p 8080:80就是在房子外面开了一扇8080的门,所有从8080进入的流量,都会引导到房子里的80端口。这个映射是单向的,从容器内访问外网不需要映射。
4.3 验证服务是否正常
然后验证容器是否真正跑起来了:
docker ps看到web-test的状态是Up,再访问验证:
curl http://127.0.0.1:8080如果返回一大段Nginx的默认HTML,说明端口映射和服务都正常,你的第一个Docker服务已经成功跑起来了。
4.4 进入容器内部看看Nginx文件在哪
有时需要进容器内部调试,比如查看配置、改页面或者检查运行日志。执行:
docker exec -it web-test bash进去之后,Nginx的默认站点目录在/usr/share/nginx/html,主配置文件在/etc/nginx/nginx.conf。你可以用cat查看,也可以临时改一个文件。但我必须强调:容器内部的修改都不会被持久化,一旦容器被删除,你改的东西全部消失。
举个例子,你可以在容器里运行:
echo "hello from docker" > /usr/share/nginx/html/index.html然后宿主机再curl http://127.0.0.1:8080,会看到新内容。但如果你执行rm删除容器再重新跑一个,Nginx就是默认页面了。这个现象不是异常,而是提醒你:需要持久化的数据,必须放到容器外面。这就是下一节要解决的问题。
4.5 清理测试容器
测试结束后,把容器停掉并删除,保持系统干净:
docker stop web-test docker rm web-test也可以一步到位,用docker rm -f web-test强制删除运行中的容器。不过建议养成先stop再rm的习惯,避免误操作把正在提供服务的东西直接删掉。
5. 数据必须留在容器外:数据卷与挂载的入门实践
我们在4.4已经看到容器内文件的临时性。生产环境里,数据库数据、日志文件、用户上传内容都必须保存在容器外部。Docker为此提供了数据卷(volume)和绑定挂载(bind mount)两种方式。
数据卷由Docker管理,放在宿主机的一个特定目录里;绑定挂载则是把宿主机上的任意目录直接挂到容器里。对新手来说,绑定挂载更直观,因为你能直接在宿主机上找到文件,备份也更简单。
5.1 最简单的绑定挂载:共享宿主机目录
我用一个例子演示:把宿主机/workspace/html挂到容器的Nginx首页目录。
docker run -d --name web-data -p 8080:80 -v /workspace/html:/usr/share/nginx/html:ro nginx:1.25命令后面的:ro表示容器内只能读不能写,挂载成只读更安全。把index.html放到宿主机的/workspace/html下,再访问容器,看到的就不是默认页面,而是你放置的内容。宿主机改,容器内立即生效,不需要重启。这套机制在调试静态网站、配置文件热更新时非常实用。
另一种方式是通过数据卷:
docker volume create html-vol docker run -d --name web-data -v html-vol:/usr/share/nginx/html nginx:1.25这时Docker在宿主机上自动分配一个目录作为数据卷。你可以用docker volume inspect html-vol看到它的具体路径。这种方式的好处是不需要关心宿主机目录具体在哪,而且可以在多个容器之间共享同一个卷。缺点是新手往往找不到文件放在哪,实际用起来不如绑定挂载直观。
5.2 用MySQL容器实战数据持久化
光说不练假把式,我们用MySQL容器来演示数据卷的威力。假设你要启动一个MySQL 8容器,并且把数据存到宿主机目录。
mkdir -p /data/mysql docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=你的密码 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这里关键点是:MySQL的数据目录是/var/lib/mysql,挂载到宿主机/data/mysql后,你在容器里建的表、写入的行,都会落在宿主机目录。把容器删掉、重跑一个同样命令的新容器,数据依然还在。
-e参数是环境变量,MySQL官方镜像通过MYSQL_ROOT_PASSWORD设置root密码。为防止中文输入混乱,密码尽量用字母数字组合。连接测试:
mysql -h 127.0.0.1 -P 3306 -u root -p能看到数据库就说明持久化生效了。往库里写点数据,再容器重建后查看,数据还在,这个操作做完,你对数据卷的理解会彻底牢固。
5.3 挂载时的权限坑:SELinux
CentOS默认开启SELinux,挂载目录时Nginx或MySQL容器内可能需要写宿主机目录。如果容器启动后报权限拒绝,很可能是SELinux拦截。两个处理办法:
- 临时调整:
sudo setenforce 0,治标不治本,重启后失效。 - 给挂载目录补充SELinux上下文:
sudo chcon -Rt svirt_sandbox_file_t /workspace/html。
更省事的方式是在挂载参数里加上:Z或:z,但需要遵循该参数的安全语义,不清楚差异时,建议先用chcon调整,再测试访问。
这个坑几乎每个在CentOS + Docker上挂过目录的人都遇到过,提前知道能省下不少时间。
6. 第一次用Docker最容易踩的坑:容器没起来、网络不通、权限卡壳
我把入门阶段最常见的几个坑集中列在这里,很多都是在自己机器上实验时试错得出的经验,一次性写给你,省得你重复踩。
6.1 坑一:docker命令报权限错误,反复出现“permission denied”
安装完Docker后,普通用户直接执行docker ps,经常会看到sock权限错误。因为Docker的socket文件/var/run/docker.sock默认只允许root用户访问。最快的方法是把当前用户加入docker组:
sudo usermod -aG docker $USER newgrp docker登出再登录,就会生效。加入docker组相当于将Docker的管理权限完全交给了该用户,因此只建议在自己信任的机器上这么操作。如果你只想临时用一下,也可以每回都加sudo。
6.2 坑二:Docker服务起不来,systemctl状态一片红
有时安装完成后systemctl start docker启动失败,查看状态提示Failed to start Docker Application Container Engine。按这套流程排查:
sudo journalctl -u docker -n 50日志里面会出现具体原因,多数是配置文件daemon.json写错了JSON格式,或者网络冲突。JSON格式错误是新手重灾区,少一个逗号、多一个引号都会导致dockerd不启动。修复后别忘记:
sudo systemctl daemon-reload sudo systemctl restart docker也遇到过内核防火墙模块(iptables)冲突的情况,如果systemctl start docker持续失败,可以用sudo dockerd --debug在终端前台启动,直接看实时日志,比反复看状态信息更直观。
6.3 坑三:端口映射成功但外部访问不到
容器启动了,curl 127.0.0.1:8080通,但从别的机器访问你的IP:8080超时。原因多半是CentOS防火墙没有放行端口。处理:
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent sudo firewall-cmd --reload如果你是刚接触服务器,也要确认云主机安全组是否放行了该端口。Docker只是把流量送到主机端口,能否从外部进来,还要看主机防火墙和云平台策略。
6.4 坑四:容器启动几秒后就自动退出
docker ps看不到容器,docker ps -a看到容器状态是Exited。原因通常不是容器崩溃,而是容器里没有长期运行的前台进程。
Docker容器不是虚拟机,它内部一定要有一个进程在跑,如果这个进程结束了,容器就退出。比如直接执行docker run centos bash,bash执行完就退出,容器生命周期结束。解决方法是:要么启动带守护性质的服务(Nginx、MySQL那种),要么在你自己的镜像里用-it进入交互模式保持终端打开:
docker run -it centos:7 bash这样你的bash不会立刻退出,容器就保持运行。
6.5 坑五:构建镜像时发现上下文太大,费时费力
如果你开始做自定义镜像,注意docker build构建时会带上当前目录(构建上下文)里的所有文件和目录。如果目录里有大文件,比如临时生成的压缩包、node_modules、.git目录,构建速度会巨慢。解决方法是:
touch .dockerignore在文件里写:
node_modules .git *.tar.gz .DS_Store这和.gitignore的语法类似,只影响构建上下文,不会影响镜像内文件。凡是用Docker构建过大型项目的人都懂这个看似微小但作用巨大的文件。
6.6 排查问题的标准姿势
最后送大家一个排查口诀:“先看docker ps -a,再看docker logs,不够就docker inspect,实在不行接上socat抓容器端口流量”。端口映射不通也可以用iptables -L -n检查Docker生成的规则,不过新手阶段,logs和inspect基本能覆盖九成问题。inspect输出的JSON虽然长,但docker inspect --format='{{.State}}' 容器名可以直接提取状态详情,比逐个翻字段快很多。
我个人在实际操作中的体会是:Docker入门最难的其实不是命令,而是心态。它是一套全新的环境管理思维,你得接受“改动默认不持久”这个前提,而不是像以前在虚拟机里那样先用起来再说。只要把镜像、容器、数据卷这三者的边界画清楚,后面学Compose、Dockerfile都会非常顺。建议你先动手把今天这六个小节从头到尾跑一遍,跑不通的地方再回来看对应的坑,基本就不会卡住了。