做开发这几年,我见过太多“在我机器上明明是好的”这种尴尬局面。环境不一致导致的部署问题,能占掉项目交付阶段一半的调试时间。Docker 就是冲着这个痛点来的——它把应用和它依赖的环境一起打包,让你在笔记本上跑起来什么样,扔到服务器上还是什么样。今天这篇是入门系列的第一篇,我们不聊复杂概念,就老老实实把最经典的 Docker HelloWorld 跑通,亲手验证一下这条从镜像到容器的工作链路到底是怎样运转的。
这篇内容适合谁?如果你是刚接触 Docker、命令行都还没摸熟的新手,这篇文章能带你从零完成安装、配置、运行、清理的完整闭环;如果你已经看过一些 Docker 教程但一直没动手,这篇文章也能帮你把零零散散的知识点串起来。我会先把环境准备讲清楚,再解释 HelloWorld 背后涉及的镜像、容器、仓库这几个核心概念,然后带你一步步敲命令跑出第一段输出,最后整理几个我实际踩过的坑和排查思路。读完你不仅能跑通 HelloWorld,还能明白它为什么能跑通。
1. 先把 Docker 装好:环境准备
1.1 选哪个版本:Docker Desktop 还是 Docker Engine
我第一次接触 Docker 的时候,第一步就被各种安装方式绕晕了。其实从使用场景来分,无非就两种选择:一种是 Docker Desktop,另一种是 Docker Engine。
Docker Desktop 适合 Windows 和 macOS 用户。它自带图形界面,装完以后系统托盘里有个小图标,点开就能看到容器状态,还能一键启动、停止、删除容器,对新手极其友好。它的底层实现是在你本机跑一个轻量级的虚拟机(macOS 上用的是 Apple 虚拟化框架,Windows 上用的是 WSL2 或 Hyper-V),Docker 命令通过客户端和这个虚拟机里的守护进程通信。换句话说,你用起来跟原生 Linux 环境没什么区别,但实际上是跑在一个特制的虚拟机里,这是最省心的方案。
Docker Engine 则是给 Linux 服务器用的。SSH 登录到一台 Ubuntu 或者 CentOS 机器上,通过包管理器安装守护进程,然后所有操作都在命令行里完成。没有图形界面,但胜在轻量、稳定,是生产环境的标配。
我的建议是:如果是本地学习,直接用 Docker Desktop;如果手头有云服务器,或者你想模拟生产环境,就装 Docker Engine。两者在命令层面没有任何区别,这篇文章里的所有命令,在两种环境下都能原样执行。
1.2 安装完先别急着跑,做一次体检
装好之后,第一件事不是急着拉镜像,而是确认安装是否完整。打开终端,敲下面两条命令:
docker version docker infodocker version会输出客户端和服务端的版本信息。你需要注意里面有没有Server这一栏。如果只有 Client 信息而 Server 显示错误或者为空,说明守护进程没起来,或者客户端连不上服务端,这时候任何 docker 命令都会报错。
docker info会给出更详细的系统信息,包括容器数量、镜像数量、存储驱动、CPU 和内存分配等。我习惯用这条命令做“体检”,确认一下资源限制是否合理。比如 Docker Desktop 默认会分配一部分内存给虚拟机,如果你本机内存本来就紧张,可以在这里看到实际占用情况。
这里有个容易踩的坑:在 Windows 上装了 Docker Desktop 之后,如果之前装了老版本的 VirtualBox 或其他虚拟化软件,可能会和 Hyper-V/WSL2 冲突,导致 Docker 启动失败。遇到这种情况,卸载冲突软件或者切换虚拟化后端能解决。macOS 上相对省心,但也要确保系统版本满足要求。
1.3 一个能让镜像拉取更顺畅的小优化
Docker 默认从官方镜像仓库拉取镜像。但在某些网络环境下,直连官方仓库的速度很不稳定,经常出现拉取到一半卡住的情况。碰到这种情况,不要怀疑 Docker 坏了,大概率是网络延迟或连接被重置导致的。
解决办法是配置镜像加速器。Docker 支持在配置文件中指定镜像仓库地址,客户端拉取镜像时会优先从加速地址获取。具体做法是找到 Docker 的配置文件(Docker Desktop 通常在设置界面的 Docker Engine 项里直接编辑 JSON,Linux 上一般在/etc/docker/daemon.json),添加类似下面的内容:
{ "registry-mirrors": ["https://your-mirror-address"] }你可以在网上搜索自己所在区域可用的镜像加速地址,填入后重启 Docker 服务即可。不过要提醒一句:加速器只影响docker pull这一步,不影响容器运行时的网络,所以配置完成后,拉取速度会明显提升,但容器内部的对外访问行为不受这个配置控制。
2. 看懂 HelloWorld 背后的两个概念
2.1 镜像和容器:一个是模具,一个是工件
很多教程一上来就让你敲docker run hello-world,敲完看到一段英文就结束了,运气好点可能打印出来了,运气不好报个错都不知道去哪查。所以我建议先花两分钟理解两个词:镜像(Image)和容器(Container)。
拿做蛋糕来打比方。镜像是模具,它规定了蛋糕的形状、配方、烘烤时间,是一个只读的模板。容器是模具脱出来的蛋糕,是镜像运行起来之后的实体。你可以用同一个模具脱出无数个蛋糕,每个蛋糕都是独立运行的,互不干扰。在 Docker 世界里,同一个镜像可以启动多个容器,每个容器都有自己的文件系统、进程空间和网络栈。
再往深一层说,镜像是分层存储的。Dockerfile 里每一行指令都会生成一个只读层,这些层叠加起来构成完整镜像。当你启动容器时,Docker 会在镜像最上层添加一个可写层,容器里所有文件改动都发生在这一层。容器删掉后,可写层随之消失,镜像本身毫发无损。这就是为什么容器能随意创建和销毁,而镜像可以保持不变。
明白这个关系之后,再看到docker run就清楚了:它是在指定镜像的基础上,创建一个新的可写层,然后启动一个进程。进程退出时,容器也就停了。
2.2 hello-world 镜像的运行机制到底是什么
hello-world这个镜像非常特殊。它体积极小(大约 20KB 左右),里面就包含一个编译好的可执行程序。当你运行它时,这个程序会向终端输出一段欢迎信息,然后立即退出。
没错,它的全部任务就是“打印一段话,然后退出”。所以很多人第一次运行时会觉得奇怪:容器启动后瞬间就结束了,用docker ps根本看不到它。这是正常的,因为容器存活的时长取决于它里面的进程是否在前台运行。hello-world里的进程执行完就退出,容器没有其他存活进程,自然也就结束了。
为了验证这个机制,你可以换个镜像试试。比如运行docker run -d nginx,nginx 会一直在前台监听端口,所以容器会一直活着,docker ps就能看到它。
理解这个点很重要,因为它解答了新手最常见的困惑:“为什么我的容器一下子就没了?”不是因为命令写错了,而是容器的生命周期就是跟着主进程走的。
2.3 为什么入门第一个项目非它莫属
动手写过代码的人都知道,学习一个新工具,最怕的不是功能复杂,而是连第一步都跑不通。hello-world就是用来干这个的:它是所有 Docker 安装教程里的标准验证程序,相当于医生用的听力计,用来测试你的耳朵到底能不能听见,也相当于程序员界的 “print(‘Hello World’)”。
它最大的优点有三个。第一是体积小,下载快。哪怕网络状况一般,也就几秒钟的事,不会让人在等待中失去耐心。第二是无依赖、零配置。不需要映射端口、不需要挂载目录、不需要设置环境变量,一条命令跑完,完全不需要额外知识。第三是验证链路完整。一条docker run命令,背后涉及客户端、守护进程、镜像仓库、镜像拉取、容器创建、进程启动、日志输出这一整条链路。只要这段输出能出现在屏幕上,就说明你机器上的 Docker 环境是通的、命令是顺的、网络是好的——所有基础条件都具备了。
所以我给新手的建议是:不管看多少理论,先亲手把hello-world跑通,用结果来确认环境没问题,再去折腾别的。否则你后面学 Docker Compose、Dockerfile 的时候,出了问题都分不清是环境问题还是配置问题。
3. 实操:手把手跑通第一个 Docker 容器
3.1 拉取镜像:docker pull 这一步发生了什么
在运行容器之前,镜像必须已经在本地。docker run命令其实自带镜像拉取逻辑:如果你指定的镜像在本地不存在,它会先尝试从仓库拉取,然后再创建容器。不过为了更清晰地理解过程,我建议先手动执行一次拉取。
docker pull hello-world你会看到类似这样的输出:
Using default tag: latest latest: Pulling from library/hello-world c1ec31eb5944: Pull complete Digest: sha256:53cc4d415d8a5b48d7d0b634b0e9d3080aa47e0e2b7d1c9c3a3f4d2d3b4e5f6a Status: Downloaded newer image for hello-world:latest docker.io/library/hello-world:latest这里有几个信息值得留意。Using default tag: latest表示你没指定版本号,Docker 默认帮你选了latest标签。c1ec31eb5944: Pull complete表示这一个镜像层下载完成。Digest是镜像内容的哈希值,用来保证镜像完整性和可信度。
需要注意的是,latest标签并不代表“最新版”,而是一个可变指针,镜像作者可以把任意版本指向这个标签。所以如果你追求稳定复现,最好指定明确的版本号,比如hello-world:latest不如hello-world@sha256:...来得精确。对于入门阶段倒不用太纠结,知道有这回事就行。
拉取完成后,可以用docker images查看本地镜像列表。你会看到 hello-world 这一行,SIZE 只有 13.3KB 左右,比普通镜像小几个数量级。
3.2 运行容器:docker run 逐词拆解
镜像就位后,正式运行:
docker run hello-world输出应该是这样的:
Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.这段信息本身就是对整个流程的翻译:客户端联系守护进程,守护进程(如果本地没有镜像)去仓库拉取 hello-world,然后创建容器、运行可执行程序,再把输出流回你的终端。建议你把这段输出多读两遍,它把 Docker 的工作流程概括得一清二楚。
现在把命令本身拆开看。docker是客户端命令,run是子命令,表示“创建并运行一个容器”,hello-world是参数,指定使用哪个镜像。完整的docker run命令格式远不止这么简单,它支持大量参数来配置容器的资源限制、网络模式、环境变量、端口映射等。比如-d表示后台运行,-it表示交互式运行,--name指定容器名,-p映射端口,-v挂载卷。HelloWorld 用不到这些参数,但理解run的本意——从镜像创建容器并启动主进程——会对后面学习一大串参数有很大帮助。
3.3 用 docker ps 和 docker logs 还原现场
由于 hello-world 容器跑完就退出了,你想确认它是否真的存在过,需要用docker ps -a查看所有容器(包括已停止的):
docker ps -a输出类似:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f1a2b3c4d5e6 hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago inspiring_brahmagupta注意这里的STATUS列显示Exited (0)。退出码 0 表示程序正常运行结束,没有异常。如果看到非 0 的退出码,说明程序运行出错了。容器会自动分配一个随机名称,比如inspiring_brahmagupta,这是 Docker 自动生成的两段单词组合,没有任何特殊含义。
如果你想看容器输出的完整日志,可以用:
docker logs f1a2b3c4d5e6这里填的是容器 ID,不一定写全,只要写前几位能唯一识别就行。docker logs在任何场景下都非常有用,尤其是排查后台容器问题时,很多信息只能从这里看到。
3.4 跑完记得清理:删除容器和镜像
跑通之后,本地会留下一个已退出的容器和一个 hello-world 镜像。虽然它们体积很小,不清理也不碍事,但我建议养成随手清理的习惯,因为以后你可能会反复创建、删除几十上百个容器,不及时清理会占用磁盘空间,还会让docker ps -a的输出变得冗长。
删除已停止的容器:
docker rm f1a2b3c4d5e6删除镜像:
docker rmi hello-world如果容器还在运行状态,docker rm会报错,需要先docker stop再删。镜像也一样,如果还有容器在用它,docker rmi会拒绝执行并提示冲突。
更省事的方式是用docker system prune一键清理所有停止的容器、悬空的镜像和未使用的网络。不过这条命令要慎用,它会一次性删除很多你可能还用得上的东西,执行前建议看一遍它会删除的清单,确认没问题再按y。
4. 常见问题速查与避坑经验
4.1 镜像拉取卡住不动,多半是网络问题
我在帮同学排查 Docker 问题时,遇到频率最高的问题就是docker pull卡住。表现形式有两种:一种是进度条长时间停在某个百分比不动,另一种是直接报net/http: TLS handshake timeout之类的错误。
遇到这种情况,第一反应不是重试,而是先看看是不是网络连接不稳定。最简单的验证方式是在同一网络环境下去访问一下 Docker Hub 官网,看能不能正常打开。如果网页都很慢,那基本可以确定是网络问题。这时候可以按照前面提到的镜像加速器方案配置一下,或者换一个网络环境再试。
如果配置了加速器还不行,还有一个冷门但有效的排查点:检查系统代理设置。如果你本机开了全局代理,Docker 守护进程不一定走这个代理,需要在 Docker 的配置里单独指定代理地址。Windows 和 macOS 的 Docker Desktop 在设置界面里都有 Proxy 选项,Linux 下则是在~/.docker/config.json里配置。
4.2 permission denied 权限错误怎么解决
在 Linux 上运行 docker 命令时,经常会遇到permission denied while trying to connect to the Docker daemon socket。这是因为 Docker 守护进程默认以 root 身份运行,它的 socket 文件/var/run/docker.sock的权限只开放给 root 用户和 docker 用户组。普通用户不在这个组里,自然没权限操作。
我建议的解决步骤是:先把当前用户加入 docker 组,然后重新登录使组权限生效。
sudo usermod -aG docker $USER执行完后,需要注销重新登录,或者运行newgrp docker让组权限立刻生效。这一步做完,再敲 docker 命令就不会有权限问题了。
补充一个安全提示:把用户加入 docker 组,等于给了这个用户近乎 root 的权限,因为 docker 本身能挂载宿主机目录、操作内核特性,是一个高权限工具。所以在服务器上要谨慎操作,别为了图方便把不该加的人加进去。
4.3 其他常见报错对照速查
这里把我实际遇到的一些报错和对应处理方式整理成表格,方便你以后排查。
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
Cannot connect to the Docker daemon | 守护进程未启动 | 启动 Docker 服务或重启 Docker Desktop |
docker: command not found | 安装未完成或没写入 PATH | 检查安装步骤,重新添加 PATH |
port is already allocated | 端口被占用 | 换一个宿主机端口,或用docker ps查看占用情况 |
No such image | 本地没有指定镜像 | 先docker pull,或确认镜像名拼写正确 |
Error response from daemon: conflict | 容器名被占用 | docker rm旧容器,或换一个名字 |
exec: "bash": executable file not found | 镜像里没有 bash | 用sh代替,或确认镜像类型是否正确 |
遇到报错时,我的习惯是先读最后一行错误信息,不要把注意力放在一大段堆栈上。Docker 的报错信息末尾通常直接点明原因,很多问题你自己就能判断。
4.4 几条实操心得,都是我踩过的坑
最后分享几个琐碎但实用的心得。
第一,命令敲错不要慌,先查docker --help。Docker 的命令体系非常规整,几乎每个命令都有对应的--help选项,而且会用docker 子命令 --help和docker help 子命令两种方式。需要时随时查,比自己死记硬背高效得多。
第二,给容器命名是一个好习惯。不加--name参数时,Docker 会生成一个随机名字,当你同时管理十几个容器时会很痛苦。哪怕是最简单的docker run --name my-hello hello-world,后面管理起来也方便很多。
第三,遇到陌生知识点,先跑一个最小例子再深入。比如你想搞懂-v挂载卷是怎么回事,就先用一条最简命令去验证,再加参数、看效果、查文档,逐步推进。这样学得最快,也不容易被一大堆概念劝退。
在我自己的实践经历里,Docker 给我最大的感受是:它把“环境一致性”这件事从玄学变成了科学。以前我们调侃“跑不起来就重装一次”,现在有了容器,环境本身就能被保存和复现。而这一切的起点,就是你能亲手把第一个 HelloWorld 跑出来,看到那句 “Hello from Docker!” 出现在屏幕上的时候,其实你已经跑通了整个 Docker 最核心的工作链路。
跑通之后,下一步可以试试用docker run -it ubuntu bash启动一个可交互的 Ubuntu 容器,在里面敲敲命令感受一下容器内外的差异;也可以学习写一个 Dockerfile,把一个简单的脚本或者网站打包成镜像。这些内容我在后续篇章里会接着拆解。你先把手上的 HelloWorld 跑通,把环境准备、镜像拉取、容器运行、日志查看这套流程走顺,后面的路会顺利得多。