在本篇实战教程中,我们将从零开始,把一个简单的 Web 应用(提供 Node.js 和 Python 两种版本源码)亲手打包成一个标准的 Docker 镜像。你将学习到如何编写核心的 Dockerfile
文件,并逐一掌握 FROM
, WORKDIR
, COPY
, RUN
, CMD
, EXPOSE
等关键指令的用法。通过 docker build
和 docker run
命令,你不仅能成功构建并运行你的第一个应用容器,还将学会如何通过端口映射从外部访问它。此外,本篇还会介绍 docker ps
, docker images
, docker stop
等常用管理命令,让你对 Docker 的日常操作了如指掌。
在上一篇文章中,我们成功运行了 hello-world
,体验了作为 Docker “消费者”的便捷。我们从 Docker Hub 下载了一个现成的镜像,并启动了它。这很酷,但还远远不够。
真正的强大之处在于,Docker 允许我们将自己的应用程序打包成标准化的、可移植的镜像。这就像给你的软件穿上了一件“金钟罩铁布衫”,无论把它部署到哪里,都能保证运行环境的一致性。
今天,我们将完成一次身份的转变:从一个镜像的消费者,变成一个镜像的创造者。准备好你的代码编辑器和终端,让我们一起将一个 Web 应用装进 Docker 容器里!
为了让大家都能轻松上手,我准备了一个极其简单的 Web 应用,它只做一件事:在访问时返回 “Hello, Docker!”。你可以根据自己的技术栈,选择 Node.js 版本或 Python 版本。
请在你喜欢的位置创建一个新文件夹,例如 my-docker-app
,然后在其中创建以下文件:
app.js
文件)// app.js
const http = require('http');
const hostname = '0.0.0.0'; // 监听所有网络接口
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, Docker! This is a Node.js App.\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
app.py
文件)# app.py
from http.server import BaseHTTPRequestHandler, HTTPServer
hostName = "0.0.0.0" # 监听所有网络接口
serverPort = 8080
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(bytes("Hello, Docker! This is a Python App.", "utf-8"))
if __name__ == "__main__":
webServer = HTTPServer((hostName, serverPort), MyServer)
print(f"Server started http://{hostName}:{serverPort}")
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
重要提示: 请注意代码中的 0.0.0.0
。在容器内,必须监听 0.0.0.0
而不是 127.0.0.1
或 localhost
,这样容器外部才能访问到它。这是新手常犯的错误!
现在,我们的应用代码准备好了。接下来,我们需要告诉 Docker 如何去构建这个应用的镜像。这个“说明书”就是 Dockerfile
文件。
在刚才的项目文件夹 (my-docker-app
) 中,创建一个名为 Dockerfile
的文件 (没有文件后缀),并写入以下内容。 (请根据你选择的应用版本,复制对应的 Dockerfile 内容)
# 1. 选择一个基础镜像
# 这是一个包含了 Node.js 运行环境的官方镜像
FROM node:18-alpine
# 2. 设置工作目录
# 在容器内创建一个 /app 目录,并作为后续命令的执行目录
WORKDIR /app
# 3. 复制文件
# 将本地的 app.js 文件复制到容器的 /app 目录中
COPY app.js .
# 4. (可选) 安装依赖 - 我们的简单应用不需要,但真实项目通常需要
# 例如: COPY package.json .
# RUN npm install
# 5. 声明容器对外暴露的端口
# 这只是一个元数据声明,告诉使用者这个容器打算使用哪个端口
EXPOSE 3000
# 6. 定义容器启动时执行的命令
# 启动 Node.js 应用
CMD ["node", "app.js"]
# 1. 选择一个基础镜像
# 这是一个非常轻量的、包含了 Python 3 的官方镜像
FROM python:3.9-slim
# 2. 设置工作目录
# 在容器内创建一个 /app 目录,并作为后续命令的执行目录
WORKDIR /app
# 3. 复制文件
# 将本地的 app.py 文件复制到容器的 /app 目录中
COPY app.py .
# 4. (可选) 安装依赖 - 我们的简单应用不需要
# 例如: COPY requirements.txt .
# RUN pip install -r requirements.txt
# 5. 声明容器对外暴露的端口
# 告诉使用者这个容器打算使用 8080 端口
EXPOSE 8080
# 6. 定义容器启动时执行的命令
# 启动 Python 应用
CMD ["python", "app.py"]
FROM
: 必须是第一条指令。指定了你的镜像要基于哪个基础镜像来构建。我们通常会选择官方的、轻量级 (alpine
, slim
) 的镜像。WORKDIR
: 设置容器内的工作目录。后续的 COPY
, RUN
, CMD
指令都会在这个目录下执行。COPY
: 将本地文件或目录复制到镜像的指定路径中。格式为 COPY <源路径> <目标路径>
。RUN
: 在构建镜像的过程中执行的命令,比如安装软件、依赖库等。每条 RUN
指令都会在镜像中创建新的一层。EXPOSE
: 声明容器在运行时对外暴露的端口。这只是一个文档性质的声明,并不会真的把端口打开。实际的端口映射是在 docker run
时指定的。CMD
: 容器启动时要执行的默认命令。如果 docker run
后面带了其他命令,CMD
会被覆盖。一个 Dockerfile 中只应该有一条有效的 CMD
。docker build
)现在我们有了应用代码和 Dockerfile
说明书,可以开始构建镜像了!
打开终端,确保你位于 my-docker-app
文件夹内,然后执行以下命令:
docker build -t my-first-app .
让我们分解这个命令:
docker build
: 这是构建镜像的命令。-t my-first-app
: -t
参数是 --tag
的缩写,用于给你的镜像打上一个标签(名字)。这里我们把它命名为 my-first-app
。.
: 这个点非常重要!它告诉 Docker 在当前目录下寻找 Dockerfile
文件,并将当前目录下的所有文件作为构建上下文 (build context) 发送给 Docker 守护进程。执行后,你会看到 Docker 按照 Dockerfile
中的步骤,一步步地执行并构建。成功后,终端会显示 Successfully tagged my-first-app:latest
。
docker run
)镜像已经躺在我们的本地仓库里了,是时候让它跑起来了!
执行以下命令来启动容器:
docker run -d -p 8000:3000 my-first-app # 如果你用的是 Node.js 版本
# 或者
docker run -d -p 8000:8080 my-first-app # 如果你用的是 Python 版本
命令解析:
docker run
: 运行容器的命令。-d
: --detach
的缩写,表示在后台运行容器,并返回容器 ID。没有它,你的终端就会被容器日志占据。-p 8000:3000
(或 8000:8080
): -p
是 --publish
的缩写,这是关键的端口映射。
-p <宿主机端口>:<容器端口>
。8000:3000
的意思是:将宿主机 (你的电脑) 的 8000 端口 映射到 容器的 3000 端口。my-first-app
: 指定要运行的镜像名称。验证一下!
现在,打开你的浏览器,访问 http://localhost:8000
。
如果一切顺利,你应该能看到页面上显示:
Hello, Docker! This is a Node.js App.
(或 Python 版本的信息)
太棒了!你已经成功地将一个 Web 应用容器化,并通过端口映射从外部访问了它!
你的容器正在后台运行,我们如何管理它呢?下面是一些你每天都会用到的命令:
查看正在运行的容器:
docker ps
你会看到类似这样的输出,包含了容器ID、镜像名、端口映射等信息。
查看所有容器 (包括已停止的):
docker ps -a
查看本地所有镜像:
docker images
你会看到 my-first-app
和之前下载的 hello-world
等镜像。
停止一个正在运行的容器:
首先通过 docker ps
找到容器的 ID (比如 f5b...
),然后执行:
docker stop <容器ID或容器名>
# 例如: docker stop f5b
删除一个已停止的容器:
docker rm <容器ID或容器名>
删除一个镜像:
删除镜像前,必须先停止并删除所有基于该镜像创建的容器。
docker rmi <镜像名或镜像ID>
# 例如: docker rmi my-first-app
今天,我们完成了从 0 到 1 的飞跃,你已经掌握了 Docker 最核心的技能:
Dockerfile
来定义镜像的构建过程。docker build
命令来创建自己的镜像。docker run
命令来启动容器,并学会了关键的端口映射。docker ps
, stop
, rm
, rmi
等日常管理命令。你构建的第一个镜像可能还不够完美,比如体积有点大,构建流程也比较简单。在下一篇文章中,我们将深入 Docker 的进阶玩法。
下一篇预告:【云原生核心技术 (4/12): Docker 进阶:镜像优化实战与 Docker Compose 揭秘】
我们将学习如何通过多阶段构建等技巧,将你的镜像体积缩小 90%!同时,我们还会介绍一个强大的工具——Docker Compose,让你能用一个文件轻松编排和管理多个关联容器(比如一个 Web 应用加一个数据库)。准备好让你的 Docker 技能更上一层楼吧!