在深入探讨各种复杂的技术细节之前,让我们先花点时间,从一个宏观的视角来鸟瞰 Docker 的全貌。这一部分,我们会从两个不同的角度出发,帮助你快速建立对 Docker 的直观理解:一个是运维工程师的视角,另一个是软件开发者的视角。
从运维的视角,我们会一起下载一个现成的“软件环境”,启动它,进入到这个环境中执行命令,最后再将它销毁。
而从开发者的视角,我们会更关注应用程序本身。我们会从代码仓库获取一份示例代码,看看如何通过一份叫做 Dockerfile 的“说明书”来为这个应用打包,并最终让它在 Docker 中运行起来。

无论你是开发者还是运维人员,理解这两个视角都将帮助你更好地掌握 Docker 的精髓。别担心,这个过程不会有太多高深的概念,我们的目标是让你对 Docker 有个感性的认识,这样在后续学习中,你就知道那些零散的知识点是如何组合在一起的。
当你安装好 Docker 后,你的电脑里其实多了两个核心组件:一个是Docker 客户端 (Client),另一个是Docker 引擎 (Engine),也叫作守护进程 (Daemon)。
你可以把 Docker 引擎想象成一个默默在后台工作的“总管”,它负责实际创建、运行和管理所有的“集装箱”。而你,就像一位“船长”,通过 Docker 客户端这个“对讲机”向“总管”发号施令。你输入的每一个 docker 命令,都是通过客户端发送给引擎来执行的。
我们可以用 docker version 命令来检查客户端和引擎是否都在正常工作,并且两者之间通信顺畅。
|$ docker version Client: Docker Engine - Community Version: 24.0.0 ... Server: Docker Engine - Community Engine: Version: 24.0.0 ...
如果你能同时看到 Client 和 Server 的信息,那就说明一切准备就绪了。
在 Docker 的世界里,镜像 (Image) 是一个非常核心的概念。 想象一下,你拿到了一套需要复杂配置才能运行的软件,整个过程可能需要安装特定的操作系统、各种依赖库、设置环境变量等等,非常繁琐。而 Docker 镜像,就像是有人已经帮你把这一切都配置妥当,然后“咔嚓”一下,拍下了一个完整的“快照”。
这个“快照”不仅包含了操作系统文件,还打包了应用程序本身以及它运行所需的所有依赖。它是一个静态的、只读的模板。对于运维人员来说,它就像一个虚拟机模板;而对于开发者来说,它更像是一个面向对象编程里的“类 (Class)”。
我们可以用 docker images 命令查看本地有哪些可用的镜像。如果你的 Docker 是新安装的,这里应该是空的。
那么,如何获取镜像呢?答案是“拉取” (pull)。让我们从 Docker Hub 这个官方的“镜像仓库”里,拉取一个最基础的 Ubuntu 系统镜像。
|$ docker pull ubuntu:latest
latest 是一个标签 (Tag),通常指向这个镜像的最新版本。拉取完成后,再用 docker images 看一下,你就会发现本地多了一个 ubuntu 镜像。
有了镜像这个“模板”,我们就可以用它来创建容器 (Container) 了。如果说镜像是“类”,那么容器就是这个“类”的一个“实例 (Instance)”。容器是动态的,是真正在运行的那个东西。
让我们用 docker run 命令来启动一个容器。
|$ docker run -it ubuntu:latest /bin/bash root@6dc20d508db0:/#
这条命令告诉 Docker:请基于 ubuntu:latest 这个镜像,创建一个新的容器,并且以交互模式 (-it) 运行,启动容器后立即执行 /bin/bash 命令,也就是打开一个命令行终端。
你会发现,你的命令提示符变了!这意味着你已经成功进入了这个全新容器的内部。这里是一个被隔离的环境,与你自己的电脑(我们称之为“宿主机”)是分开的。
容器提供了一个隔离的运行环境。你在容器里做的任何操作,比如安装软件、删除文件,通常不会影响到宿主机或其他容器,就像你在一个沙盒里玩耍一样。
现在,你在容器里可以像在一个全新的、迷你的 Ubuntu 系统里一样执行命令。比如,用 ps 命令查看一下当前正在运行的进程。你会发现,这里非常干净,只有我们刚刚启动的 bash 进程和 ps 命令本身。
当你探索完毕,可以按下 Ctrl + D 或者输入 exit 来退出并终止这个容器。如果你想退出但不终止它,可以按下 Ctrl + P 再按 Ctrl + Q。
我们可以用 docker ps 命令来查看当前正在运行的容器。如果加上 -a 参数 (docker ps -a),则可以看到所有容器,包括已经停止的。
如果你想重新进入一个正在后台运行的容器,可以使用 docker exec 命令。最后,当我们不再需要一个容器时,可以用 docker stop 来停止它,再用 docker rm 来彻底删除它。
对于开发者而言,Docker 最大的魅力在于它解决了“在我电脑上明明是好的”这个世纪难题。它能将你的应用程序和它所需的一切依赖“打包”在一起,交付给任何人,都能保证运行环境的一致性。这个过程,我们称之为**“容器化” (Containerizing)**。
要实现容器化,我们需要先编写一份“说明书”,告诉 Docker 如何一步步地构建我们应用的镜像。这份说明书,就是 Dockerfile。
Dockerfile 是一个纯文本文件,里面包含了一系列的指令。每一行指令都对应一个操作,比如“基于哪个基础镜像开始”、“拷贝哪些文件到镜像里”、“需要安装什么依赖”、“对外暴露哪个端口”以及“容器启动后默认执行什么命令”。
下面是一个简单的 Node.js 应用的 Dockerfile 示例:
|# 使用一个轻量的 Alpine Linux 作为基础镜像 FROM alpine # 安装 Node.js 和 npm RUN apk add --update nodejs nodejs-npm # 将当前目录下的所有文件拷贝到镜像的 /src 目录 COPY . /src # 设置工作目录为 /src WORKDIR /src # 安装应用依赖 RUN npm install # 声明容器将要监听的端口 EXPOSE 8080 # 设置容器启动时执行的命令 ENTRYPOINT ["node", "./app.js"]
这份文件就像一份菜谱,清晰地描述了如何从零开始,制作出一份包含我们应用程序的“大餐”。
有了 Dockerfile 这份菜谱,我们就可以请 Docker 这个“大厨”来为我们烹饪了。使用的命令是 docker build。
|$ docker build -t my-app:1.0 .
-t 参数用来给新构建的镜像起一个名字和标签,这里是 my-app:1.0。最后的 . 表示 Dockerfile 在当前目录下。
构建完成后,用 docker images 就能看到我们亲手创建的 my-app:1.0 镜像了。
接下来,就是见证奇迹的时刻!用 docker run 运行我们自己的应用容器。
|$ docker run -d -p 8080:8080 --name my-first-app my-app:1.0
这里的 -d 参数表示让容器在后台运行。-p 8080:8080 是一个关键的端口映射步骤,它把我们电脑(宿主机)的 8080 端口,连接到了容器内部的 8080 端口。这样,我们就可以通过访问宿主机的 8080 端口来使用容器里的应用了。
现在,打开你的浏览器,访问 http://localhost:8080,你应该就能看到你的应用界面了!
通过开发者视角,我们完成了一个完整的“容器化”流程。这个流程可以用下图来清晰地展示:
至此,你已经从两个核心视角体验了 Docker。你不仅知道如何管理别人做好的容器,也学会了如何将自己的应用打包成标准、可移植的容器。这就是 Docker 的强大之处:一次构建,处处运行 (Build once, run anywhere)。