IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

上一篇文章,我们把 Docker 环境搭起来了,也跑了一个 hello-world 做冒烟测试。但那个容器打印完消息就退出了,你甚至来不及感受“容器到底是什么”。今天,我们来点真格的——启动一个 Nginx Web 服务器,从拉取镜像、运行容器、访问服务,再到查看日志、管理生命周期,一条龙走一遍。

读完这一篇,你对 Docker 的操作手感就基本有了。后面的镜像分层、Dockerfile、网络、存储,都是在这个手感上逐步加码。

一、从 hello-world 到 Nginx:第一次真正的服务

回想一下上篇的 docker run hello-world。那个命令实际上做了四件事,理解这四件事,就理解了 Docker 工作的全链路:

客户端发出命令 → Docker Daemon 接收 → 检查本地是否有镜像
→ 没有就去仓库拉取 → 基于镜像创建容器 → 容器运行并输出 → 容器退出

hello-world 是个“一次性脚本镜像”——输出完就退出,容器也随之终止。但我们平时部署的服务(Nginx、Redis、MySQL、我们自己写的 Web 应用),它们是需要持续运行的。让容器“持续运行”的关键,是容器内有一个前台进程在持续监听。如果前台进程退出,整个容器就会停止。

下面我们以 Nginx 为例,体验一次完整的“长期运行的服务容器”。

二、Docker 三大核心概念

在动手之前,先建立三个概念。这三个概念是 Docker 的骨架:

用一张图来理解它们的关系:

docker pull → 从 Registry 下载 Image → docker run → 基于 Image 创建 Container

一个镜像可以启动多个容器(就像一套 CentOS ISO 可以装多台服务器),每个容器之间相互隔离,各自拥有独立的文件系统、进程空间和网络栈。

三、拉取镜像:把 Nginx 请到本地

3.1 docker pull:从仓库下载镜像

输出如下:

latest: Pulling from library/nginx
2d35ec5109b2: Pull complete
8b8e7c31c414: Pull complete
f6a7c44ac56a: Pull complete
f3af02bc16c2: Pull complete
Digest: sha256:42e917aaa1b5bb40dd0f6f7f4f2e6b8a0a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

每一行 Pull complete 对应镜像的一个层(Layer)。Docker 镜像采用分层存储,每个层包含一些文件和配置,层与层之间叠加形成完整的文件系统。hello-world 只有一个层所以秒下完,而 Nginx 镜像有多个层,体积更大。

3.2 验证镜像拉取成功

输出:

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    42e917aaa1bb   2 weeks ago   187MB
hello-world  latest    d2c94e258dcb   8 months ago  13.3kB
  • REPOSITORY:镜像名称,library/nginx 的简写

  • TAG:版本标签,latest 表示最新版

  • IMAGE ID:镜像唯一标识(SHA256 的前 12 位)

  • SIZE:镜像体积,注意这是压缩后的大小,实际部署后会更大

对比一下:hello-world 才 13.3kB,而 Nginx 是 187MB。这个差距我们在第 3 篇详细拆解。

关于 latest 标签latest 只是 Docker 的默认标签,并不保证一定是“最新稳定版”。生产环境中,建议使用具体版本号(如 nginx:1.25),避免因自动更新导致不兼容。

3.3 国内用户:配置镜像加速

如果你在执行 docker pull 时卡住很久,或者报超时错误,说明 Docker Hub 在你所在的网络环境访问较慢。Docker 默认从 Docker Hub 拉取镜像,但由于网络原因,国内访问经常很慢甚至超时。解决方案是配置国内的镜像加速器。

Docker Desktop(Windows/macOS)用户:

右键点击系统托盘 Docker 图标 → SettingsDocker Engine,在 JSON 配置中添加 registry-mirrors

{
  "registry-mirrors": [
    "https://docker.xuanyuan.me"
  ]
}

点击 Apply & Restart 使配置生效。

Linux 用户:

创建或编辑 /etc/docker/daemon.json

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://docker.xuanyuan.me"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

验证加速是否生效:

docker info | grep "Registry Mirrors"

如果输出了你配置的镜像地址,说明配置成功。

四、运行第一个 Nginx 容器

4.1 docker run:启动!
docker run --name my-nginx -p 8080:80 -d nginx:latest

命令解析(每一个参数都很重要):

执行后,返回一长串字符:

c7f2e8a1b4d5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1

这是容器 ID(完整版 64 位 SHA256)。Docker 的大部分操作只需要前 4 位即可唯一识别这个容器。

4.2 验证容器运行状态

输出:

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                  NAMES
c7f2e8a1b4d5   nginx:latest   "/docker-entrypoint.…"   5 seconds ago   Up 5 seconds   0.0.0.0:8080->80/tcp   my-nginx

关键列解读:

4.3 访问 Nginx 服务

打开浏览器,访问 http://localhost:8080,你会看到 Nginx 的欢迎页面:

Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working.

也可以在终端验证:

curl http://localhost:8080

会输出 Nginx 欢迎页的 HTML 源码。这说明 Nginx 容器正在正常提供服务。

4.4 端口映射为什么是必要的?

容器拥有独立的网络命名空间,默认情况下容器内的端口无法从宿主机直接访问。-p 8080:80 在宿主机和容器之间建立了一条“端口隧道”,让外部流量能够到达容器内部的 Nginx 服务。如果你不指定 -p,Nginx 仍然会在容器内运行,但你无法从浏览器访问到它。

五、容器操作:查看、停止、启动、删除

容器启动后,我们还需要掌握基本的生命周期管理。

5.1 查看日志

输出示例:

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
...
2025/01/15 10:30:00 [notice] 1#1: start worker process 30

这些是 Nginx 的启动和运行日志。生产环境中,docker logs 是最常用的排错手段之一。

实时跟踪日志(-f 持续输出,--tail 限制行数):

docker logs -f --tail=50 my-nginx
5.2 查看容器详细信息

输出的是一个巨大的 JSON 对象,包含容器的 ID、IP 地址、端口映射、挂载信息、环境变量等全部元数据。虽然首次看到可能有点懵,但你可以用 --format 提取关键字段:

# 查看容器 IP 地址
docker inspect --format='{{.NetworkSettings.IPAddress}}' my-nginx

这个 IP 是 Docker 在桥接网络中自动分配的,容器的 IP 在每次重启后可能变化(这也是为什么后面 K8s 需要 Service 来做稳定入口的原因)。

5.3 进入容器内部

Docker 推荐使用 exec 进入正在运行的容器,而不是已经被废弃的 attach

docker exec -it my-nginx /bin/bash

参数解释:-i 保持 STDIN 打开(交互),-t 分配一个伪终端。执行后你会发现终端提示符变了:

这说明你已经进入了 Nginx 容器的内部。可以看到容器内的文件系统:

bin  boot  dev  docker-entrypoint.d  docker-entrypoint.sh  etc  home  lib  ...

再退出容器(exit),回到宿主机。

5.4 停止容器

stop 会向容器内的主进程(PID 1)发送 SIGTERM 信号,给应用一段“优雅关闭”的时间(默认 10 秒)。10 秒后如果进程还没退出,Docker 会发送 SIGKILL 信号强制终止。生产环境中应优先使用 docker stop,避免数据损坏。

验证:

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

没有 my-nginx 了。

但如果你加 -a 参数(显示所有容器,包括已停止的):

CONTAINER ID   IMAGE          STATUS                      NAMES
c7f2e8a1b4d5   nginx:latest   Exited (0) 2 seconds ago    my-nginx

容器退出了,但依然存在(文件系统被保留,可以重启或查看日志)。

5.5 重启已停止的容器

start 会重新启动一个已经停止的容器,且之前的配置(端口映射、名称、数据卷)都保留。

restart = stop + start,常用于服务重新加载。

重启策略:对于需要自动恢复的服务,可以在 docker run 时指定 --restart=always,让 Docker 守护进程在容器退出后自动重启它。

5.6 强制终止容器

kill 直接发送 SIGKILL 信号,进程被立即终止,不给“优雅关闭”的时间,可能导致数据丢失。非必要不使用,仅在容器卡死无法正常停止时才考虑。

5.7 删除容器
# 先停止,再删除
docker stop my-nginx
docker rm my-nginx

# 或者:强制删除运行中的容器(不推荐)
docker rm -f my-nginx

删除后,docker ps -a 中不会再出现这个容器,但镜像仍然存在。如果连镜像也要删除:

六、容器生命周期全景图

Docker 容器有以下核心状态:

docker pull → 镜像在本地
     ↓
docker create → Created(容器已创建,文件系统就绪,但未启动)
     ↓
docker start → Running(主进程正在运行)
     ↓            ↓
docker stop → Stopped(优雅停止)    docker kill → 强制终止
     ↓            ↓
docker start → 重新进入 Running
     ↓
docker rm → Deleted(容器完全删除)

理解这个状态机非常重要。后续我们学 Kubernetes 时,Pod 的生命周期设计思想与这套状态机一脉相承。

七、进阶操作:一个容器,运行多个服务实例

同一个镜像可以启动多个容器,每个容器跑在独立的隔离环境中,互不干扰。我们演示一下:

# 启动三个 Nginx 容器,分别映射不同端口
docker run -d --name web1 -p 8081:80 nginx:latest
docker run -d --name web2 -p 8082:80 nginx:latest
docker run -d --name web3 -p 8083:80 nginx:latest

docker ps
CONTAINER ID   IMAGE          PORTS                  NAMES
a1b2c3d4e5f6   nginx:latest   0.0.0.0:8081->80/tcp   web1
b2c3d4e5f6a7   nginx:latest   0.0.0.0:8082->80/tcp   web2
c3d4e5f6a7b8   nginx:latest   0.0.0.0:8083->80/tcp   web3

三个 Web 服务同时运行,各自使用独立的内存和文件系统,这就是容器隔离能力的直观体现。这个能力正是 Kubernetes 水平伸缩的基础——需要更多实例?从同一个镜像多启动几个容器就好。

清理三个容器:

docker rm -f web1 web2 web3

八、作为系列贯穿项目:Flask + Redis 计数器初探

这个系列从第 10 篇开始,会围绕一个 Flask + Redis 访问计数器应用逐步深入。今天我们可以先跑一个极简版本,感受一下“多组件应用在容器中运行”是什么样子。

8.1 启动 Redis 容器
docker run -d --name my-redis -p 6379:6379 redis:alpine

拉取 Redis 的 Alpine 精简版镜像(体积远小于标准版,生产环境也推荐 Alpine 镜像),然后启动容器:

Unable to find image 'redis:alpine' locally
alpine: Pulling from library/redis
...
Status: Downloaded newer image for redis:alpine
a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3
8.2 启动 Flask 应用容器

使用一个已经准备好的镜像(你也可以基于 Python 镜像自己构建,我们第 4 篇会手把手写 Dockerfile):

docker run -d --name my-flask -p 5000:5000 --link my-redis:redis python:3.12-alpine \
  sh -c "pip install flask redis && python -c '
from flask import Flask
import redis
app = Flask(__name__)
r = redis.Redis(host=\"redis\", port=6379, decode_responses=True)

@app.route(\"/\")
def index():
    count = r.incr(\"hits\")
    return f\"Hello! This page has been visited {count} times.\"

app.run(host=\"0.0.0.0\", port=5000)
'"

注意:--link 是一个较老的参数,这里仅用于快速演示。实际上我们可以在后面创建一个自定义网络让容器之间通信,第 8 篇会有详细讲解。

8.3 验证效果
curl http://localhost:5000
Hello! This page has been visited 1 times.

多执行几次:

curl http://localhost:5000
# Hello! This page has been visited 2 times.
curl http://localhost:5000
# Hello! This page has been visited 3 times.

每刷新一次,计数器就 +1。这个简单的例子实际上已经是一个“微服务架构”了——Flask 是 Web 服务,Redis 是缓存/存储服务,两者通过容器网络通信。从第 10 篇开始,我们会把这两个服务一步步部署到 Docker Compose 和 Kubernetes 上。

8.4 清理
docker rm -f my-flask my-redis

九、国内环境特殊方案:离线导入镜像

如果你所处的网络环境非常受限,连镜像加速器都不可用,还有一个办法:在另一台网络条件好的机器上把镜像导出为 tar 包,再拷贝到目标机器导入。

在有网的机器上:

# 拉取镜像
docker pull nginx:latest

# 导出镜像为 tar 文件
docker save -o nginx.tar nginx:latest

在目标机器上:

# 导入镜像
docker load -i nginx.tar

之后就可以正常使用这个镜像启动容器了。这种离线方式也常用于内网服务器环境。

十、本篇总结与命令速查

这一篇我们完整经历了 Docker 容器的核心操作流程:

  • 拉取镜像:docker pull,理解分层下载

  • 启动容器:docker run,掌握 --name-p-d 三大核心参数

  • 查看状态:docker psdocker ps -a

  • 查看日志:docker logsdocker logs -f

  • 进入容器:docker exec -it

  • 管理生命周期:docker stop / start / restart / kill / rm

  • 镜像管理:docker imagesdocker rmi

  • 离线导入导出:docker save / load

常用命令速查表:

从下一篇开始,我们将深入 Docker 最核心的设计理念:镜像的分层结构。理解了这个,你才能真正理解“为什么镜像构建快、为什么容器启动秒级、为什么能一次构建处处运行”——这些都不是魔法,而是设计。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维!

更多推荐