云原生核心技术 (3/12): Docker 实战入门——亲手构建并运行你的第一个 Web 应用容器

摘要

在本篇实战教程中,我们将从零开始,把一个简单的 Web 应用(提供 Node.js 和 Python 两种版本源码)亲手打包成一个标准的 Docker 镜像。你将学习到如何编写核心的 Dockerfile 文件,并逐一掌握 FROM, WORKDIR, COPY, RUN, CMD, EXPOSE 等关键指令的用法。通过 docker builddocker run 命令,你不仅能成功构建并运行你的第一个应用容器,还将学会如何通过端口映射从外部访问它。此外,本篇还会介绍 docker ps, docker images, docker stop 等常用管理命令,让你对 Docker 的日常操作了如指掌。


引言:从“消费者”到“创造者”

在上一篇文章中,我们成功运行了 hello-world,体验了作为 Docker “消费者”的便捷。我们从 Docker Hub 下载了一个现成的镜像,并启动了它。这很酷,但还远远不够。

真正的强大之处在于,Docker 允许我们将自己的应用程序打包成标准化的、可移植的镜像。这就像给你的软件穿上了一件“金钟罩铁布衫”,无论把它部署到哪里,都能保证运行环境的一致性。

今天,我们将完成一次身份的转变:从一个镜像的消费者,变成一个镜像的创造者。准备好你的代码编辑器和终端,让我们一起将一个 Web 应用装进 Docker 容器里!


一、准备我们的 Web 应用

为了让大家都能轻松上手,我准备了一个极其简单的 Web 应用,它只做一件事:在访问时返回 “Hello, Docker!”。你可以根据自己的技术栈,选择 Node.js 版本或 Python 版本。

请在你喜欢的位置创建一个新文件夹,例如 my-docker-app,然后在其中创建以下文件:

选项 A: Node.js 版本 (创建 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}/`);
});
选项 B: Python 版本 (创建 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.1localhost,这样容器外部才能访问到它。这是新手常犯的错误!


二、编写你的第一个 Dockerfile

现在,我们的应用代码准备好了。接下来,我们需要告诉 Docker 如何去构建这个应用的镜像。这个“说明书”就是 Dockerfile 文件。

在刚才的项目文件夹 (my-docker-app) 中,创建一个名为 Dockerfile 的文件 (没有文件后缀),并写入以下内容。 (请根据你选择的应用版本,复制对应的 Dockerfile 内容)

Dockerfile for Node.js 版本
# 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"]
Dockerfile for Python 版本
# 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"]
Dockerfile 指令详解
  • 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 端口
    • 这样,所有访问你电脑 8000 端口的流量,都会被 Docker 转发到容器的 3000 端口上,也就是我们 Node.js 应用监听的端口。
  • 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 技能更上一层楼吧!

你可能感兴趣的:(云原生:从,Docker,入门到,K8s,实战精通,云原生,docker,前端)