咱们直接说正事。这套课的第一节“Docker基础知识(课时1)”,讲的就是你第一次接触容器技术时,最该搞明白的那几件事:Docker到底解决了什么问题、镜像和容器是什么关系、常用的基础命令怎么用、有哪些坑会反复踩到。不扯Kubernetes,不聊Swarm,也不讲网络编排,把最基础的底子打牢。
我见过不少刚接触容器的人,第一天就跑去背命令,结果连“镜像”和“容器”的区别都说不清楚,遇到报错更是全靠百度。这一篇我就把第一节课的内容掰开揉碎,按我实际讲课和带新人的顺序来写。不管你是学生、转行的,还是项目里被迫接手运维工作的开发,这篇内容都能直接用。
1. 先搞懂Docker到底在解决什么问题
很多人一上来就学命令,学到一半才发现自己压根不知道为什么要这么搞。所以第一课,我习惯先花大量时间讲“它解决什么问题”,再碰命令。
1.1 从“环境地狱”说起
先说一个你再熟悉不过的场景:代码在自己电脑上跑得好好的,提交到服务器上就跑不起来了。检查半天,发现服务器上的Python版本不对、缺少某个动态库、环境变量没配好、配置文件路径不一样。
以前我们怎么解决?要么写一堆安装文档,让别人照着一步步配环境;要么用虚拟机,把整个操作系统一起打包带走。但这些方案都有老大难的问题:安装文档永远跟不上环境变化,步骤漏一步就全废;虚拟机倒是环境一致,可一个镜像动辄几个GB甚至十几个GB,启动还慢,运维成本高得离谱。
我举个例子,一个做Java开发的朋友跟我说,他每次部署新环境都要花一整天装依赖、调配置。后来项目组引入Docker,把这些步骤全部固化到镜像里,部署时间从一天缩到几分钟。这种体验的差别,用过的都知道。
1.2 Docker是拿什么思路解决的
Docker的核心思路不复杂,一句话:把应用连同它的运行环境一起打包成一个标准化的交付单元。
什么意思?你做Web开发时用过依赖管理工具吧。Node项目用package.json记录依赖版本,Python用requirements.txt。Docker把这种“依赖锁定”的思路从代码层面扩展到了操作系统层面。它把代码、运行时、系统库、配置全部打包进一个东西里,这个东西就是镜像。你在自己电脑上能跑,换一台机器也一样能跑,因为运行环境全在里面,跟宿主机没啥关系了。
再对比一下虚拟机和Docker容器的差别。虚拟机是虚拟出一整套硬件,然后在上面运行一个完整的操作系统;Docker容器则直接复用宿主机内核,只把应用需要的用户空间文件打包进去。所以Docker镜像体积小得多,启动速度也快得多。同样一台机器,虚拟机可能只跑三五个实例,Docker容器跑几十个都很正常。
这里需要说明一点,Docker容器不完全等同于虚拟机,它做不到虚拟机那么彻底的隔离。同一台宿主机上的容器共享内核,如果内核出问题,所有容器都会受影响。但在绝大多数应用场景下,这种隔离程度已经完全够用了。第一课先把“容器比虚拟机轻量、便携”这个认知建立起来,后面学网络、存储时再逐步加深理解。
1.3 镜像、容器、仓库:一次搞清这三个概念
第一节课最容易卡住的概念,就是镜像和容器的关系。我打个比方:镜像就像程序里的类定义,容器就是根据这个类实例化出来的对象。也可以理解成镜像是“菜谱”,容器是“按菜谱做出来的菜”。你可以用同一份菜谱做无数盘菜,每盘菜有各自的调味、形态,但它们遵循相同的做法。
镜像本身是只读的,里面装好了应用代码、运行时环境、依赖库。当你用docker run启动镜像时,Docker会在镜像之上加一层可写层,运行中的文件修改都发生在这一层。这一层加上镜像一起运行起来的东西,就是容器。
仓库则负责存储和分发镜像。你从仓库拉取别人的镜像,也可以把自己做的镜像推到仓库分享给别人。第一个阶段只要知道docker pull是从仓库拉镜像,docker push是推镜像就够了。
我见过有人问“为什么容器停掉再启动,之前改的文件都没了”。理解了上面的机制就不奇怪了:容器里的可写层随着容器生命周期走,容器删除后这层就没了。解决办法是挂载数据卷,这在后面会专门讲。
2. 环境准备和安装验证
先把Docker装好,否则后面的命令都跑不起来。这一节我按主流平台说一下安装要点和验证方法。
2.1 不同平台的安装方式
Linux平台,特别是服务器环境,最常用的方式是用包管理器直接安装。以Debian/Ubuntu系为例,一般步骤是更新索引、安装依赖、添加官方源、再安装docker-ce。用yum或dnf的CentOS/Rocky系思路也差不多。具体命令我会在后面演示,但原则就一条:用发行版官方源的Docker版本,不要图省事直接从网上复制一段来历不明的脚本。
macOS和Windows桌面用户,官方推荐Docker Desktop。这东西内置了图形界面,能看到镜像列表、容器列表、日志,还支持一键启动、停止。Windows用户注意后台类型是WSL2还是Hyper-V,优先用WSL2,资源占用更小。需要注意Docker Desktop对商业公司有收费要求,个人学习和开源项目使用不受影响,但如果你所在团队是正规商业公司,记得先查清楚授权。
还有一种常见情况:某些服务器是离线内网环境,不能直接访问外网。这时候就需要手动下载对应系统的安装包传到内网安装。Debian系用的是 .deb 包,RHEL系用 .rpm 包,下载时务必选对CPU架构,x86_64和ARM64的包不能混用,否则后面启动服务会报错。
2.2 验证安装:hello-world 到底干了什么
装完后在终端输入 docker version,能正常显示Client和Server信息,说明Docker服务已经跑起来了。然后执行那个最经典的验证命令:
docker run hello-world很多新手搞不懂,这条命令到底发生了什么。简单说,Docker会先看本地有没有hello-world这个镜像,没有就去仓库拉取,拉下来后放到容器里执行。容器跑完一段欢迎语后退出,任务完成。
我第一次跑这条命令时其实很懵,因为什么都没看懂。后来我建议新手把这条命令拆开来看:docker run是“启动容器”的命令,hello-world是镜像名。Docker自动完成了“本地找不到镜像→从仓库拉取→创建容器→执行→退出”的完整流程。这一条命令背后涉及的知识点,就是镜像、仓库、容器三者的协作关系,跟你后面掌握所有命令都有关。
2.3 配置镜像加速:提升拉取速度的关键一步
安装完Docker,第一个让人头大的问题就是拉镜像慢。特别是访问Docker官方仓库(Docker Hub),速度经常让人怀疑人生。
解决办法是配置镜像加速器。国内有很多团队维护公共的镜像加速地址,不需要注册就能用。具体做法是编辑Docker的daemon.json配置文件,把加速地址加进registry-mirrors字段,然后重启Docker服务。
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://你的加速地址"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker配置完可以用 docker info 查看 Registry Mirrors 一栏是否生效。注意加速地址是第三方维护的,时效性不稳定,如果某天突然发现某个地址拉不动了,及时换其他可用地址。另外如果拉取的是比较小众的镜像,加速源里没有缓存,反而可能更慢,这时可以临时去掉加速配置,直接从官方仓库拉。
3. 镜像和容器的生命周期管理
这是第一课的实操重点。我会按“拿镜像→看镜像→跑容器→管容器→清理容器”的顺序讲,顺便把那些文档里写得比较绕的命令统一解释一遍。
3.1 拿镜像与查镜像
从仓库拉取镜像,用docker pull。比如拉一个Nginx的轻量版本:
docker pull nginx:alpinenginx是镜像名,alpine是标签。标签用于区分同一个镜像的不同版本,常用的有latest、alpine、slim等。alpine标签表示基于Alpine Linux构建,体积小,适合做基础镜像;latest则不一定是最新版,可能只是该仓库默认指向的标签。
看完本地有哪些镜像,用:
docker images它会列出仓库名、标签、镜像ID、创建时间和大小。镜像是分层的,所以你会看到部分镜像共享中间层,导致docker images显示的总大小比实际占用磁盘空间大。这点在清理空间时会遇到,后面我再细说。
想查看镜像的详细信息,可以用 docker inspect 镜像名。返回的是一个很长的JSON,里面包含环境变量、端口暴露、挂载点、架构等各种信息。第一课不需要全看懂,只要知道有这么个查看入口就行了。
3.2 启动和管理容器
跑一个容器最基础的命令是:
docker run --name web-demo -d -p 8080:80 nginx:alpine拆开看各参数含义:--name给容器命名,不指定就随机生成;-d表示后台运行;-p 8080:80是把宿主机的8080端口映射到容器的80端口,这样外部访问宿主机的 http://localhost:8080 就等于访问容器里Nginx的80端口。最后是镜像名。
跑起来后,用 docker ps 看正在运行的容器,加 -a 参数能看到所有容器,包括已经退出的。docker logs 容器名或ID可以看日志,这对排查应用问题非常关键。想进到容器内部看现场,用:
docker exec -it web-demo bash这里-it是两个参数组合:-i保持标准输入打开,-t分配一个伪终端。合起来的含义就是“以交互方式进入容器内部”。很多第一次接触的人忘了加-it,结果发现按什么都没反应,不是命令错了,而是缺了这两个参数。
停止容器用 docker stop 容器名,启动已停止的容器用 docker start 容器名。注意start和run的区别:run是从镜像新建容器,start是启动现有容器。删除容器用 docker rm 容器名,删除前最好先stop,否则会提示冲突;可以用 docker rm -f 强制删除运行中的容器。
有个细节值得强调:容器是临时性的。你创建一个容器,在里面改了配置文件,如果把这个容器删了,所有改动就都没了。所以第一课就要养成习惯:容器应该被当作一次性进程来看待,持久的数据要放在数据卷里,不要存在容器的可写层里。
3.3 从容器生成镜像:commit 与 Dockerfile 初识
把容器改完之后,如果想保存当前状态做成新镜像,可以用:
docker commit web-demo mynginx:v1这个命令会把容器当前文件系统保存成新镜像。不过在真实项目中,commit方式强烈不推荐。原因很简单:commit会让镜像层变得极其杂乱,还容易把容器内的临时文件、历史操作痕迹都固化进去,别人拿到这种镜像根本不知道它是怎么构建出来的,也没法复现。
生产环境里构建镜像要用Dockerfile。第一课只需要看懂最基本的形式,比如:
FROM nginx:alpine COPY index.html /usr/share/nginx/html/index.html EXPOSE 80FROM定基础镜像,COPY把本机文件复制进镜像,EXPOSE声明容器对外端口。在项目目录下放好一个index.html,用 docker build -t my-nginx:v1 . 就能构建出镜像。这里最后的点表示构建上下文路径,Docker打包时会把这个目录下的文件都发给Docker守护进程,所以不要把没用的文件放在构建目录里。学到这里就够了,详细的Dockerfile指令后面专门开课讲。
4. 数据持久化和端口映射
容器本身隔离性很强,但这也带来了两个问题:数据容易丢,外部访问麻烦。第一课必须把这两个基础操作搞明白。
4.1 为什么容器里的数据会“丢”
前面说过,容器文件系统在容器删除后就没了。你可能跑了几个星期的服务,写了不少日志或业务数据,一不小心执行了 docker rm,瞬间全部清空。而且容器报错退出后,docker logs还在,但容器里的文件不会保留,想找现场都找不到。
这个“丢”不是消失了,而是容器生命周期结束,底层存储层被回收了。所以容器适合跑无状态的服务,比如定时任务、计算任务、Web服务的无状态副本。凡是有状态的数据,一律要放到宿主机的存储里。
4.2 数据卷:把数据放在容器外
Docker提供数据卷(volume)机制,把宿主机目录或一块独立存储挂载进容器。最直接的用法是绑定挂载,指定宿主机目录和容器目录的映射关系:
docker run -d --name web-demo -v /opt/webdata:/usr/share/nginx/html -p 8080:80 nginx:alpine这条命令把宿主机 /opt/webdata 目录挂载到容器的 /usr/share/nginx/html 目录。容器里对 /usr/share/nginx/html 下文件的读写,实际都落在宿主机 /opt/webdata 上。就算容器删了,数据还在宿主机上。后续你再起一个新容器,挂载同一个目录就行,数据自动接上。
另一种方式是用具名卷,不直接绑定宿主机目录,而是让Docker管理一个卷:
docker volume create web-data docker run -d --name web-demo -v web-data:/usr/share/nginx/html -p 8080:80 nginx:alpine具名卷的好处是不依赖宿主机具体路径,迁移时也更方便,适合对接后续要学的docker compose。绑定挂载则更直接,适合开发调试,因为你可以直接改宿主机上的代码文件,容器里立即生效。两种方式各有适用场景,第一课先都记住就行。
4.3 端口映射:把容器里的服务暴露到外部
容器默认网络是隔离的,宿主机外部访问不到。要让外部能访问,就得做端口映射。前面的 -p 8080:80 就是干这个的。第一条是宿主机端口,第二条是容器端口,流量进到宿主机8080端口后,会转发到对应容器的80端口。
映射多个端口就写多个-p参数:
docker run -d --name app -p 8080:8080 -p 8443:8443 myapp:v1还有一种不指定宿主端口的写法:
docker run -d -P --name app myapp:v1大写-P会把Dockerfile里EXPOSE声明的端口随机映射到宿主机的某个高位端口,用 docker ps 就能看到实际映射关系。这种写法适合快速测试,但不适合生产,因为端口不稳定。
端口冲突是新手经常遇到的事。如果你启动容器时报“port is already allocated”,说明宿主机的这个端口已经被其他进程占用了。排查方法一种是看是不是别的容器占用了:
docker ps -a另一种是直接用系统命令查端口占用。不管哪种方式,结论都是换一个宿主机端口,或者停掉占用端口的容器。第一课不需要深入网络模型,只要理解“宿主端口独立分配、容器内端口是应用监听的目标”就足够了。
5. 新手必修:常见报错与排查技巧
基础命令学完以后,你会遇到各种奇奇怪怪的报错。这里把第一课最常碰到的问题整理成一张速查表,后面遇到直接对号入座。
5.1 常见报错速查表
| 报错内容 | 原因 | 处理办法 |
|---|---|---|
| Cannot connect to the Docker daemon | Docker服务没启动,或当前用户不在docker用户组 | 启动服务:sudo systemctl start docker; 或者 sudo usermod -aG docker 你的用户名,重启终端 |
| port is already allocated | 宿主机端口被占用 | 换端口,或停掉占用的容器 |
| no matching manifest for linux/amd64 in the manifest list | 镜像不支持当前CPU架构 | 换对应架构的镜像标签,或用多架构镜像 |
| Get ... context deadline exceeded | 拉取镜像超时,网络问题 | 检查网络,配置镜像加速,或重试 |
| Unable to find image ... locally | 本地没有镜像,拉取也失败 | 先确认镜像名是否正确,再检查网络和仓库访问 |
| exec: "bash": executable file not found | 容器里没有bash | 改成 sh,例如 docker exec -it 容器名 sh |
| OCI runtime create failed | 容器运行时创建失败,常见于权限或内核问题 | 查看完整报错,确认是否用了不兼容的镜像,检查SELinux或AppArmor配置 |
表格里每一行背后都有具体场景。比如“Cannot connect to the Docker daemon”这个错误,几乎每个新手都遇过。实际上很多Linux发行版的Docker服务默认是开机自启的,但如果你的环境是刚装好的服务器,服务可能还没启动,或者你当前用户的权限不够。第一课就要养成用 journalctl -u docker 看Docker服务日志的习惯,里面会写明原因,比乱猜强得多。
5.2 容器状态码和排查思路
容器退出后,docker ps -a 会显示Exit Code。这个数字不是随机的,是在容器里运行的进程退出状态码。常见的有:
| 状态码 | 含义 | 排查方向 |
|---|---|---|
| 0 | 正常退出 | 检查是否预期内完成,比如一次性任务跑完就退出 |
| 1 | 程序运行出错 | 看容器日志:docker logs 容器名 |
| 125 | docker run命令本身出错 | 检查docker run参数和宿主环境 |
| 126 | 容器内命令无法执行 | 检查权限,或命令文件是否可执行 |
| 127 | 命令找不到 | 可能依赖缺失、PATH不对,或基础镜像里没有该命令 |
| 137 | 被强制终止 | 最常见是内存溢出被系统杀掉,查内存和OOM日志 |
| 139 | 内存段错误 | 一般与应用自身有关,检查代码和依赖库 |
我举一个实际例子。有一次我帮人排查一个nginx容器,一直启动失败,docker logs显示正常,但状态码是137。后来一查,是宿主机内存不足,内核把容器杀掉了。这种情况你去容器配置里找问题永远找不到,必须看宿主机层面的日志。
5.3 命令记不住怎么办
第一课信息量不小,命令又多,记不住很正常。我不建议死记硬背,我自己也记不住所有参数。常用的做法有三个:一是用 docker --help 查看帮助,比如 docker run --help 会列出所有参数;二是给常用命令设置shell别名,比如把 docker compose 简化为 dc;三是依赖图形界面工具。Docker Desktop里可以可视化查看容器和镜像,对新手熟悉概念很有帮助。
帮一个新同事排查问题时,经常发现他们连命令名字都是问一遍查一遍,效率极低。我建议第一课就把 docker pull、docker run、docker ps、docker logs、docker exec、docker stop、docker rm 这七个命令练到形成肌肉记忆,其他命令用到时再查手册,完全没问题。
6. 第一节课后应该达到什么状态
基础课学完,不是记住了多少命令,而是能独立完成一个完整流程。我按这个标准来设计自测点,对应前面所有知识点。
6.1 自测清单
你可以按顺序做一遍,能顺利走完基本就算过关了:
- 拉取一个nginx:alpine镜像。
- 查看本地镜像列表,找到刚拉下来的镜像。
- 用这个镜像启动一个名为 test-web 的容器,后台运行,宿主机8088端口映射到容器80端口。
- 在浏览器访问 http://localhost:8088 ,能看到Nginx欢迎页。
- 用 docker logs 查看容器日志。
- 进入容器内部,看看 /usr/share/nginx/html 下的文件。
- 把宿主机某个目录挂载到容器里,修改宿主机文件后刷新页面确认生效。
- 停止并删除这个容器,再确认挂载目录里的数据还在。
做到第7步和第8步时,你基本就打通了“镜像→容器→存储→端口”这条主线。这个流程看似简单,但它涵盖了Docker最核心的使用方法。后面无论是学Docker Compose、写生产级Dockerfile,还是接触Kubernetes,基础都是这些。
6.2 接下来的学习方向
第一课结束后,下一步通常是补齐两个方向的短板:一是把Dockerfile写规范,包括多阶段构建、镜像瘦身、基础镜像选型;二是学docker compose 做多容器编排。在这之前,你先花点时间把单容器的生命周期和持久化玩熟练,地基不牢后面会加倍还债。
我每年都会带一些新人和实习生,他们最容易犯的错误就是想一口气把所有内容吞下去。上完课就想立刻学Kubernetes,结果连容器日志在哪看都不知道。我的建议是,第一周只做一件事:用Docker把你熟悉的一个应用跑起来,让它能稳定访问、日志正常、数据不丢。做到这一步,你对容器的理解已经超过相当一部分工作了几年但从不深究的人了。
最后分享一个我自己常用的检查小技巧。拿到一台装有Docker的机器,第一件事永远是 docker version 和 docker info,快速确认服务状态、存储驱动、并发上限等基础信息。第二件事是 docker ps -a,看看当前环境里有什么存量容器。这两步做完,再动手操作会踏实很多。很多线上问题,其实是在环境状态都没搞清楚的情况下就开始跑命令造成的。第一课打好这个“先看状态再动手”的习惯,后面能帮你省下大把排查时间。