导读

你是否遇到过这种场景:“在我电脑上明明能跑啊”?代码换了台机器就报各种缺库报错。传统解决方式是重装系统或手动配置环境,但效率极低。Docker 就是来解决这个问题的——它把程序和所有依赖打包成一个"集装箱",搬到任何地方开箱即用。本篇教程从零讲起,不需要你有运维经验,只要会用命令行就能跟完全程。

参考基准:本教程内容结构参考 Runoob Docker 教程,命令均经过验证。国内访问 Runoob 慢的话可以用浏览器直接搜索"runoob docker"找到镜像站阅读。


一、什么是容器?和白话对比虚拟机

在说 Docker 之前,先看看传统的**虚拟机(VM)**是怎么工作的:

想象你要同时运行两个不同的操作系统应用。虚拟机的做法是:在每个 VM 里安装一个完整的操作系统(叫 Guest OS),然后再装你的程序。每个 VM 都要占用几 GB 空间和几分钟启动时间——就像每栋楼都带一个独立的发电厂。

Docker 容器的思路完全不同。容器不模拟整个操作系统,而是直接共享宿主机的内核(Linux 内核),只隔离出进程所需的资源。结果呢?

维度虚拟机(VM)Docker 容器
是否自带完整 OS是(几百 MB ~ GB 级)否,共享宿主机内核
启动速度数分钟秒级(通常 < 1s)
单个应用的内存开销几个 GB几十到几百 MB
一台机器能跑的实例数几个到十几个上百个

画个图直观理解 Docker 的架构层次(从下往上):

  1. 宿主机:你实际操作的电脑或服务器(可以是 Linux、Windows 或 macOS)
  2. Linux 内核:提供命名空间(隔离)和 cgroup(资源限制)
  3. Docker 运行时:负责创建和管理容器
  4. 容器:你的应用程序在其中运行的轻量级沙箱

Docker 容器化架构图

💡 一句话总结:虚拟机 = 完整的房子;Docker 容器 = 公寓里的独立房间。后者更省资源、更快落地。


二、四个核心概念

2.1 镜像(Image)—— 只读的模板

镜像就像是容器的"蓝图"或"安装包"。它是一个只读的文件,里面包含了运行程序所需的一切:代码 + 运行时环境 + 系统工具 + 系统库

类比:镜像 = 一张 DVD 光盘——可以反复复制使用,但不能往里写数据。

# 查看本地有哪些镜像
docker images

# 从远程仓库拉取一个镜像(默认从 Docker Hub)
docker pull nginx:latest
# ↑ 这行命令会从 Docker Hub 下载 Nginx 的最新稳定版镜像(约 140MB)

⚠️ 国内注意:Docker Hub(hub.docker.com)在国内拉取很慢甚至超时。解决办法见 §7「国内加速方案」。

2.2 容器(Container)—— 镜像的运行实例

容器是从镜像创建出来的可执行实例。同一个镜像可以同时跑多个容器,它们互相隔离、互不影响。

类比:如果镜像是 DVD 光盘,容器就是光盘放进光驱后正在播放的画面。

# 用 nginx 镜像创建一个容器并后台运行(-d = detached,后台模式)
docker run -d -p 80:80 --name my-nginx nginx
# -d          :后台运行,不阻塞终端
# -p 80:80    :端口映射,宿主机的 80 端口 → 容器的 80 端口
# --name      :给容器起个名字,方便后续管理
# nginx       :使用的镜像名(自动用之前拉取的镜像)

# 查看所有运行中的容器
docker ps

# 查看所有容器(包括已停止的)
docker ps -a

打开浏览器访问 http://localhost,你会看到 NGINX 的欢迎页——这说明容器已经成功运行了。

2.3 卷(Volume)—— 数据的持久化

容器的文件系统是临时的——删掉容器,里面的数据就没了。如果你想保留数据(比如数据库文件),就需要用到卷(Volume)

类比:容器是一个 U 盘(插上有数据,拔掉就没了),卷就是外接硬盘(拔了还在)。

# 创建一个命名的卷
docker volume create my-data

# 运行容器时挂载这个卷(-v 参数)
docker run -d -v my-data:/var/lib/mysql --name my-mysql mysql:8.0
#                  ^^^^^^^^^^^^^^^^^^
#         格式:卷名 : 容器内的路径
#   写在 /var/lib/mysql 的数据会永久保存在 my-data 卷中,即使删除容器也不会丢

# 查看已有卷
docker volume ls

💡 为什么需要卷:假设你在容器里放了一个网站数据库,哪天你需要扩容或修复,删掉旧容器换上新的——如果没有卷,数据就全丢了。

2.4 网络(Network)—— 容器之间的通信

默认情况下,容器之间是隔离的,不能互相访问。Docker 提供了网络机制让容器可以安全地通信。

类比:每台容器都是一间办公室,默认门是锁着的。Docker 网络就是给你们发同一组门禁卡,持有相同卡的办公室之间可以互通。

# 查看 Docker 默认网络
docker network ls

# 创建一个自定义网络(推荐做法)
docker network create app-net

# 运行容器并加入指定网络
docker run -d --name web --network app-net nginx
docker run -d --name db --network app-net mysql:8.0
# 现在 web 和 db 可以通过容器名称互相 ping 通了!

三、第一个实验:跑通一个 Nginx 容器

理论讲完了,动手试试。以下是一步步操作:

步骤 1:拉取 Nginx 镜像

docker pull nginx:latest
# 首次下载约 140MB,国内可能需要几分钟(加速方案见 §7)

步骤 2:启动 Nginx 容器

docker run -d -p 8080:80 --name my-web nginx
#            ^^^^ ^^
#            宿主机端口 : 容器内端口
#   把容器的 80 端口映射到宿主机的 8080,避免和已有的 Web 服务冲突

步骤 3:验证运行

# 方法 1:curl 检查
curl http://localhost:8080
# 应返回 Nginx 欢迎页的 HTML

# 方法 2:看容器日志
docker logs my-web
# 输出容器内 Nginx 的运行日志

步骤 4:进入容器内部

docker exec -it my-web /bin/bash
# -i :保持标准输入开放(交互式)
# -t :分配一个伪终端
# 进入后可以执行 ls、cat 等命令查看容器内部文件系统
# 退出用 exit 命令

步骤 5:挂载自定义页面

如果你想用自己的 HTML 文件替换默认的欢迎页:

# 在宿主机创建自定义页面
mkdir -p ~/my-site
echo "<h1>Hello from Docker!</h1>" > ~/my-site/index.html

# 重新启动容器,把宿主机的目录挂载到容器内
docker rm -f my-web                                    # 删除旧容器
docker run -d -p 8080:80 -v ~/my-site:/usr/share/nginx/html --name my-web nginx
#                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#                            宿主机目录 : 容器内目标目录

刷新浏览器,你会看到自己的页面。


四、自己动手:编写 Dockerfile 制作专属镜像

上面用的都是别人做好的镜像。如果我要跑一个 Python 项目怎么办?这就需要自己构建镜像——答案是写一个 Dockerfile

Dockerfile 是一个文本文件,包含了一系列指令,告诉 Docker 如何一步步构建镜像。类比:Dockerfile 是一道菜的菜谱,镜像是最终端上来的菜。

4.1 一个简单的 Python 应用

先在宿主机创建一个示例项目:

mkdir -p ~/my-python-app && cd ~/my-python-app

# 创建一个简单的 Flask Web 应用
cat > app.py << 'EOF'
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello from Docker + Flask!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
EOF

# 创建一个 requirements.txt(列出依赖)
cat > requirements.txt << 'EOF'
flask==3.0.0
EOF

4.2 编写 Dockerfile

在同目录下创建 Dockerfile没有后缀名):

# 第 1 行:基于哪个镜像开始构建
# python:3.12-slim 是官方精简版 Python 镜像(约 120MB,比完整版小很多)
FROM python:3.12-slim

# 设置容器内的工作目录(相当于 cd /app)
WORKDIR /app

# 将当前目录的 requirements.txt 先复制到容器里
COPY requirements.txt .

# 安装 Python 依赖(等价于 pip install -r requirements.txt)
RUN pip install --no-cache-dir -r requirements.txt

# 复制所有项目文件到容器
COPY . .

# 声明容器对外暴露的端口(文档用途,实际还需 -p 参数映射)
EXPOSE 5000

# 容器启动时执行的命令
CMD ["python", "app.py"]

📝 关键字解释

  • FROM:基准镜像,所有 Dockerfile 必须以这行开头
  • WORKDIR:设置后续指令的工作目录
  • COPY:从宿主机复制文件到容器
  • RUN:在容器内执行一条命令(如安装软件),每条 RUN 产生一层新的镜像层
  • EXPOSE:声明端口(不等同于映射,仅说明信息)
  • CMD:容器启动后的默认命令

4.3 构建并运行

# 构建镜像(-t 打标签,最后一句 . 表示上下文是当前目录)
docker build -t my-flask-app:latest .

# 运行容器
docker run -d -p 5000:5000 --name my-flask my-flask-app

# 验证
curl http://localhost:5000
# 输出:Hello from Docker + Flask!

✅ **恭喜!**你已经完成了 Docker 最核心的工作流:写 Dockerfile → 构建镜像 → 运行容器


五、进阶:用 docker-compose 一键拉起 MySQL + Redis

真实项目中通常会同时运行多个服务——Web 前端、API 后端、数据库、缓存……逐个 docker run 很麻烦。这时候需要 docker-compose

Docker Compose 是一个编排工具:你用一份 YAML 文件描述所有服务及其关系,然后一条命令全部拉起。类比:以前要一个一个点启动按钮,compose 就是一个"总开关"。

5.1 安装 docker-compose

# docker-compose 目前已经集成到 docker CLI 插件中(Docker Desktop 自带)
# 如果你单独安装了 Docker Engine,可以用以下方式验证:
docker compose version
# 输出类似:Docker Compose version v2.xx.x

# 如果提示找不到,也可以通过 apt 安装:
sudo apt update && sudo apt install -y docker-compose-plugin

5.2 编写 docker-compose.yml

创建一个项目目录并写入配置文件:

mkdir -p ~/my-stack && cd ~/my-stack
# docker-compose.yml
# 定义所有服务的拓扑结构,一条命令全部启动
version: "3.9"

services:
  # ── Web 应用 ──────────────────────────────
  web:
    image: nginx:latest          # 直接使用官方 nginx 镜像
    ports:
      - "8080:80"                 # 宿主机 8080 → 容器 80
    volumes:
      - ./html:/usr/share/nginx/html   # 挂载自定义页面
    depends_on:
      - redis                     # 等待 redis 就绪后再启动
    networks:
      - backend                   # 连接到后端网络

  # ── MySQL 数据库 ──────────────────────────
  db:
    image: mysql:8.0             # MySQL 8.0 官方镜像
    environment:
      MYSQL_ROOT_PASSWORD: root123  # 设置 root 密码(生产环境务必更换!)
      MYSQL_DATABASE: myapp       # 自动创建一个名为 myapp 的数据库
      MYSQL_USER: appuser         # 创建一个普通用户
      MYSQL_PASSWORD: apppass     # 该用户的密码
    ports:
      - "3306:3306"               # 暴露 MySQL 端口供本机连接
    volumes:
      - db-data:/var/lib/mysql    # 数据持久化到卷
    networks:
      - backend

  # ── Redis 缓存 ────────────────────────────
  redis:
    image: redis:7-alpine        # Alpine 精简版(体积最小)
    ports:
      - "6379:6379"               # 暴露 Redis 端口
    volumes:
      - redis-data:/data          # 持久化缓存数据
    networks:
      - backend

# ── 命名卷定义 ──────────────────────────────
volumes:
  db-data:                        # MySQL 数据卷
  redis-data:                     # Redis 数据卷

# ── 网络定义 ────────────────────────────────
networks:
  backend:                        # 自定义桥接网络
    driver: bridge

5.3 一键启动

# 在 docker-compose.yml 所在目录执行:
cd ~/my-stack

# --build:有 Dockerfile 的项目才会用到(本次纯用镜像,不用加)
docker compose up -d
# ↑ -d = detached,后台运行

# 查看各服务状态
docker compose ps
# 应该看到 3 个服务都在 Running 状态

# 查看实时日志
docker compose logs -f

# 单独查看某个服务的日志
docker compose logs -f db

5.4 验证各服务

# ① 网页:访问 http://localhost:8080

# ② MySQL:进入数据库客户端
docker exec -it my-stack-db-1 mysql -uroot -proot123 -e "SHOW DATABASES;"
# 或用 docker-compose 的 exec(新版写法):
docker compose exec db mysql -uroot -proot123 -e "SHOW DATABASES;"

# ③ Redis:进入 Redis CLI
docker compose exec redis redis-cli ping
# 输出 PONG = 正常

# ④ 容器间通信:web 可以通过服务名 "redis" 直接访问
# (这就是 networks 的作用——通过名称解析而不是 IP)

5.5 常用管理命令

# 停止所有服务(保留容器和卷)
docker compose down

# 停止并删除容器(数据卷保留)
docker compose down

# 停止并彻底删除(包括网络和匿名卷)
docker compose down -v

# 重启某个服务
docker compose restart db

# 重新构建并启动(改完 Dockerfile 后用)
docker compose up -d --build

# 查看资源占用
docker stats

💡 云间 API 中转站cloudzone-api.cyou)的用户除了用 Docker 部署应用外,也可以直接用他们的云端 OpenAI 兼容 API——支持 Anthropic 模型、国内直连、香港节点、0.05x 起价,无需自建基础设施。对于开发测试阶段,API 方式往往比搭容器更快。


六、常用运维命令速查表

这份清单涵盖日常使用中最常碰到的命令,建议收藏备用。

容器管理

# 列出运行中的容器
docker ps

# 列出所有容器(含已停止的)
docker ps -a

# 启动/停止/重启容器
docker start <容器名或ID>
docker stop <容器名或ID>
docker restart <容器名或ID>

# 强制删除容器
docker rm -f <容器名或ID>

# 查看容器详细配置(JSON 格式)
docker inspect <容器名或ID>

# 查看容器内实时资源占用
docker stats

# 进入运行中容器的终端
docker exec -it <容器名或ID> /bin/bash
# Alpine 基础镜像无 bash,改用:sh

# 查看容器日志
docker logs <容器名或ID>
docker logs -f <容器名或ID>     # 跟随式日志(像 tail -f)
docker logs --tail 100 <容器名>  # 只看最后 100 行

镜像管理

# 搜索远程仓库的镜像
docker search nginx

# 拉取镜像(指定版本)
docker pull nginx:1.25

# 给镜像打标签
docker tag nginx:latest myregistry.com/nginx:v1

# 推送镜像到私有仓库(需要先 docker login)
docker push myregistry.com/nginx:v1

# 查看镜像分层历史(排查"镜像怎么这么大"问题)
docker history nginx:latest

# 删除镜像(确保没有容器在用)
docker rmi <镜像名或ID>

# 清理不再使用的镜像(批量清理,慎用)
docker image prune -a

卷管理

# 列出所有卷
docker volume ls

# 创建命名卷
docker volume create my-volume

# 查看卷的详细信息
docker volume inspect my-volume

# 删除未使用的卷(释放磁盘空间)
docker volume prune

网络管理

# 列出所有网络
docker network ls

# 创建自定义网络
docker network create my-net

# 查看网络详情
docker network inspect my-net

# 断开容器与网络的连接
docker network disconnect my-net <容器名>

# 连接容器到指定网络
docker network connect my-net <容器名>

# 删除网络
docker network prune

清理与垃圾回收

# 删除所有已停止的容器
docker container prune

# 删除所有悬空镜像(<none> 标签的)
docker image prune

# 删除所有未使用的资源(容器 + 镜像 + 卷 + 网络)
# ⚠️ 谨慎执行!这会删除所有不在运行的容器和悬空镜像
docker system prune -a

# 查看详细磁盘占用
docker system df

七、国内加速方案

在中国大陆,Docker Hub 拉取镜像经常极慢或超时。解决方法如下:

方案 A:配置 Docker 镜像加速器(推荐)

/etc/docker/daemon.json 中添加 mirror 地址:

# 编辑(不存在则新建)
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com"
  ]
}
EOF

# 重启 Docker 使配置生效
sudo systemctl daemon-reload
sudo systemctl restart docker

📌 说明:以上使用的是腾讯云镜像加速器(免注册即可用)。你也可以去阿里云容器镜像服务获取个人专属的加速地址(每个阿里云账号有自己的唯一 URL,速度更好)。其他常见源还有中科大 https://docker.mirrors.ustc.edu.cn、网易 http://hub-mirror.c.163.com。配置好后,docker pull 会自动走镜像源。

配置完成后验证:

# 确认 mirror 已生效
docker info | grep -A5 "Registry Mirrors"
# 应看到你配置的地址

# 测试拉取(这次应该会快很多)
docker pull alpine:latest

方案 B:使用国内替代镜像仓库

部分开源项目也提供了完整的镜像同步仓库,比如:

用法:把原来 docker pull library/nginx 改为 docker pull registry.example.com/nginx


八、小结

这篇教程覆盖了 Docker 的核心知识链条:

  1. 容器 vs 虚拟机:共享内核、轻量快捷
  2. 四大概念:镜像(模板)、容器(实例)、卷(持久化)、网络(通信)
  3. 跑通第一个容器:Nginx 一键启动
  4. 写 Dockerfile:构建你自己的专属镜像
  5. docker-compose:多服务编排,一条命令拉起全栈

掌握了这些,你已经具备了容器化部署的基本能力。下一步可以深入了解:多阶段构建(减小镜像体积)、CI/CD 流水线集成、Kubernetes 集群编排等进阶主题。

💡 想快速体验但不想自己配环境?云间 API 中转站cloudzone-api.cyou)提供 90+ 模型的 OpenAI/Anthropic 兼容 API,按量计费、国内直连,注册即送额度。对于不需要自建推理服务的场景,这是最省心的选择。


附:一键安装脚本(双版本)

下方提供了 Linux/macOS/WSL 的 Bash 脚本和 Windows PowerShell 脚本。公开源码,欢迎审查——不放心可以先复制给任何 AI 判断逻辑。

Linux / macOS / WSL

#!/usr/bin/env bash
set -u
# ============================================================
#  Docker 容器化部署实战 — 一键安装脚本(macOS / Linux / WSL)
#  公开源码,欢迎审查 —— 不放心可先复制给 AI 判断
#  功能:检测系统 → 配置国内镜像加速 → 安装 Docker → 验证运行
# ============================================================

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'

info()  { echo -e "${GREEN}[INFO]${NC} $1"; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
step()  { echo -e "${CYAN}[STEP]${NC} $1"; }

# ---------- 第一步:网络环境检测 ----------
detect_network() {
  step "检测网络环境(国内 / 国外)..."
  if curl -fsI --max-time 5 "https://claude.ai" >/dev/null 2>&1; then
    info "可直连 claude.ai,判定为海外网络"
    echo "overseas"
  else
    warn "无法直连 claude.ai,判定为国内网络环境(将自动切换国内镜像源)"
    echo "domestic"
  fi
}

# ---------- 第二步:检测系统 ----------
detect_os() {
  step "检测操作系统..."
  if command -v swsystemctl >/dev/null 2>&1 || command -v service >/dev/null 2>&1; then
    echo "linux"
  elif command -v uname >/dev/null 2>&1 && uname -s | grep -qi darwin; then
    echo "macos"
  else
    error "不支持的操作系统。请使用 Linux、macOS 或 WSL(Windows Subsystem for Linux)。"
    exit 1
  fi
}

# ---------- 第三步:配置 Docker 镜像加速器 ----------
configure_mirror() {
  local net="$1"

  # macOS 不走 daemon.json,跳过
  [ "$(detect_os)" = "macos" ] && {
    info "macOS 使用 Docker Desktop,镜像加速请在 Docker Desktop 设置中配置"
    return 0
  }

  if [ "$net" = "domestic" ]; then
    step "配置 Docker 国内镜像加速..."
    sudo mkdir -p /etc/docker

    # 优先使用阿里云专属加速(每个用户可在控制台获得唯一地址)
    # 如果不知道地址,就用腾讯云的公共加速
    ALI_MIRROR=""
    if [ -n "${DOCKER_REGISTRY_MIRROR:-}" ]; then
      ALI_MIRROR="$DOCKER_REGISTRY_MIRROR"
    fi

    if [ -n "$ALI_MIRROR" ]; then
      MIRROR_URL="$ALI_MIRROR"
      info "使用指定的加速地址:$MIRROR_URL"
    else
      MIRROR_URL="https://mirror.ccs.tencentyun.com"
      info "未检测到阿里云专属加速地址,使用腾讯云公共加速:$MIRROR_URL"
      echo "  · 如需更好的加速效果,前往 https://cr.console.aliyun.com 获取个人专属地址"
      echo "    设置为环境变量:export DOCKER_REGISTRY_MIRROR='https://xxxxxx.mirror.aliyuncs.com'"
    fi

    # 读取已有配置(合并方式)
    EXISTING=""
    if [ -f /etc/docker/daemon.json ]; then
      EXISTING=$(cat /etc/docker/daemon.json)
    fi

    cat > /tmp/docker-daemon-new.json << DAEMONEOF
{
  "registry-mirrors": ["$MIRROR_URL"]
}
DAEMONEOF

    # 备份原配置
    if [ -f /etc/docker/daemon.json ]; then
      cp /etc/docker/daemon.json /etc/docker/daemon.json.bak.$(date +%Y%m%d%H%M%S)
      info "已备份原配置到 /etc/docker/daemon.json.bak.*"
    fi

    # 写入新配置(简单覆盖,如需合并现有 key 请手动处理)
    sudo cp /tmp/docker-daemon-new.json /etc/docker/daemon.json
    info "镜像加速配置完成"

    # 重启 Docker 服务
    step "重启 Docker 服务..."
    if command -v systemctl >/dev/null 2>&1; then
      sudo systemctl daemon-reload
      sudo systemctl restart docker
    elif command -v service >/dev/null 2>&1; then
      sudo service docker restart
    else
      error "无法自动重启 Docker。请手动执行:sudo systemctl restart docker"
      return 1
    fi
    info "Docker 服务已重启"
  else
    info "海外网络,跳过镜像加速配置(可使用默认 Docker Hub)"
  fi
}

# ---------- 第四步:检查 Docker 是否已安装 ----------
check_docker_installed() {
  step "检查 Docker 是否已安装..."
  if command -v docker >/dev/null 2>&1; then
    local VERSION
    VERSION=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
    info "Docker 已安装:$(docker --version 2>/dev/null)"
    # 询问是否重新安装
    read -r -p "是否重新安装最新版本?[y/N]: " reinstall
    if [ "${reinstall:-N}" = "y" ] || [ "${reinstall:-N}" = "Y" ]; then
      return 1  # 重新安装
    fi
    info "跳过安装,继续验证..."
    return 0
  fi
  return 1
}

# ---------- 第五步:安装 Docker ----------
install_docker() {
  step "安装 Docker Engine(此步骤需要 sudo 权限)..."
  info "使用官方安装脚本(https://get.docker.com),失败后将回退 apt 安装"

  # 尝试官方安装脚本
  if curl -fsSL --max-time 60 "https://get.docker.com" -o /tmp/docker-install.sh 2>/dev/null; then
    info "官方安装脚本下载成功"

    if sudo bash /tmp/docker-install.sh; then
      info "Docker 安装成功(官方脚本)"
      rm -f /tmp/docker-install.sh
      return 0
    fi

    warn "官方脚本安装失败,尝试备用方案..."
    rm -f /tmp/docker-install.sh
  else
    warn "官方安装脚本下载失败,直接使用 apt 安装..."
  fi

  # 备用方案:apt 安装
  step "通过 apt 安装 Docker(备用方案)..."

  # 安装前置依赖
  sudo apt-get update >/dev/null 2>&1 || {
    sudo yum update -y >/dev/null 2>&1 || {
      error "包管理器(apt/yum)更新失败,请检查网络连接后重试"
      return 1
    }
  }

  # 添加 Docker 官方 GPG key
  if ! sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FFC804346B1D2142F4A4349E98CDFBBDE4FC5 2>/dev/null; then
    # 新版 Debian/Ubuntu 使用 /etc/apt/keyrings/
    sudo mkdir -p /etc/apt/keyrings 2>/dev/null
    curl -fsSL --max-time 30 "https://download.docker.com/linux/ubuntu/gpg" 2>/dev/null | \
      sudo dd of=/etc/apt/keyrings/docker.asc >/dev/null 2>&1
    sudo chmod a+r /etc/apt/keyrings/docker.asc
  fi

  # 添加 Docker 源(根据发行版自动选择)
  local OS_TYPE
  if grep -qi ubuntu /etc/os-release 2>/dev/null; then
    OS_TYPE="ubuntu"
  elif grep -qi debian /etc/os-release 2>/dev/null; then
    OS_TYPE="debian"
  else
    OS_TYPE="debian"  # 默认 fallback
  fi

  local ARCH
  ARCH=$(dpkg --print-architecture 2>/dev/null || uname -m)
  case "$ARCH" in
    arm64) ARCH="arm64" ;;
    amd64) ARCH="amd64" ;;
    aarch64) ARCH="arm64" ;;
    x86_64) ARCH="amd64" ;;
    *) warn "未知架构 $ARCH,尝试 amd64"; ARCH="amd64" ;;
  esac

  local SOURCE_LINE="deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${OS_TYPE} $(lsb_release -cs 2>/dev/null || grep -oE '[0-9]+' /etc/os-release | head -1 | sed 's/1[0-9]/\0/') stable"
  echo "$SOURCE_LINE" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null 2>&1 || true

  sudo apt-get update >/dev/null 2>&1 || true

  if sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 2>/dev/null; then
    info "Docker 安装成功(apt 方案)"
    return 0
  fi

  error "所有安装方案均失败。请按以下指引手动安装:"
  echo "  1. 海外用户:curl -fsSL https://get.docker.com | sudo sh"
  echo "  2. 国内用户:先配置镜像加速(§7),再执行上述命令"
  echo "  3. macOS 用户:下载 Docker Desktop(https://docs.docker.com/desktop/install/mac-install/)"
  return 1
}

# ---------- 第六步:授权当前用户管理 Docker ----------
setup_docker_user() {
  step "配置 Docker 用户权限(非 sudo 也能运行 docker 命令)..."
  if id -nG "$USER" 2>/dev/null | grep -qw docker; then
    info "当前用户已在 docker 用户组中"
  else
    sudo usermod -aG docker "$USER"
    info "已将 $USER 加入 docker 用户组"
    echo "  · 此操作无需恢复(移除请用:sudo gpasswd -d $USER docker)"
    echo "  · 新开终端窗口或使用 newgrp docker 使权限生效"
  fi
}

# ---------- 第七步:验证安装 ----------
verify_installation() {
  step "验证 Docker 安装..."

  # 拉取 hello-world 镜像
  if docker run --rm hello-world >/dev/null 2>&1; then
    info "hello-world 容器运行成功!Docker 安装正常。"
  else
    warn "hello-world 未能运行,尝试拉取镜像..."
    docker pull hello-world >/dev/null 2>&1
    if docker run --rm hello-world >/dev/null 2>&1; then
      info "hello-world 容器运行成功!"
    else
      error "Docker 安装后无法运行容器。可能是权限问题。"
      echo "  请执行:sudo usermod -aG docker \$USER && newgrp docker"
      echo "  然后重新运行本脚本。"
      return 1
    fi
  fi

  # 跑一个 nginx 示例验证
  step "运行 Nginx 示例容器进行端到端验证..."
  if docker run -d -p 8080:80 --name cr-docker-test nginx; then
    sleep 2
    if curl -fs --max-time 5 "http://localhost:8080" >/dev/null 2>&1; then
      info "Nginx 容器运行正常!访问 http://localhost:8080 可看到欢迎页。"
    else
      warn "容器已启动但 curl 不通(可能被防火墙拦截),请手动检查:docker ps"
    fi
    docker rm -f cr-docker-test >/dev/null 2>&1
  else
    error "Nginx 示例容器启动失败,请检查 Docker 状态:systemctl status docker"
    return 1
  fi

  return 0
}

# ---------- 主流程 ----------
main() {
  echo "============================================"
  echo "  Docker 容器化部署 — 一键安装脚本"
  echo "  适用于 macOS / Linux / WSL"
  echo "============================================"
  echo ""

  local NET
  NET=$(detect_network)

  local OS
  OS=$(detect_os)

  configure_mirror "$NET"

  if ! check_docker_installed; then
    install_docker || {
      error "Docker 安装失败。请查看上方错误信息后手动处理。"
      echo "  · 官网安装指南:https://docs.docker.com/engine/install/"
      exit 1
    }
  fi

  setup_docker_user
  verify_installation

  echo ""
  echo "============================================"
  info "全部完成!"
  echo "  常用命令速查:"
  echo "    docker ps              # 查看运行中的容器"
  echo "    docker images          # 查看本地镜像"
  echo "    docker run -d -p 80:80 nginx  # 启动一个 Nginx 容器"
  echo "    docker compose up -d   # 用 compose 一键拉起全部服务"
  echo ""
  echo "  更多实践请参考正文教程。"
  echo "============================================"
}

main "$@"

Windows PowerShell

# ============================================================
#  Docker 容器化部署实战 — 一键安装脚本(Windows PowerShell)
#  公开源码,欢迎审查 -- 不放心可先复制给 AI 判断
#  说明:Docker 在 Windows 上需通过 Docker Desktop 或 WSL 2
#        本脚本检测 WSL 2 环境并在其中执行安装(一站式完成)。
# ============================================================
$ErrorActionPreference = "Stop"

function Write-Info  { Write-Host "[INFO] $args" -ForegroundColor Green }
function Write-Warn  { Write-Host "[WARN] $args" -ForegroundColor Yellow }
function Write-Err   { Write-Host "[ERROR] $args" -ForegroundColor Red }
function Write-Step  { Write-Host "[STEP] $args" -ForegroundColor Cyan }

# 1. 检测 WSL 2
Write-Step "检查 WSL 2 环境..."
try {
    $wslVersion = wsl -l -v 2>$null | Select-String "docker" | Out-String
    $wslExists = Test-Path /proc/version -pathType Leaf
    if (-not ($wslExists)) {
        $wslExists = (Get-Command wsl -ErrorAction SilentlyContinue) -ne $null
    }
} catch {
    $wslExists = $false
}

if (-not $wslExists) {
    Write-Err "未检测到 WSL。请先安装 WSL 2:"
    Write-Host "  1. PowerShell(管理员)执行:wsl --install"
    Write-Host "  2. 重启电脑"
    Write-Host "  3. 设置 Ubuntu 用户名和密码"
    Write-Host "  4. 重新运行本脚本"
    Write-Host ""
    Write-Host "或者:下载 Docker Desktop for Windows"
    Write-Host "  https://docs.docker.com/desktop/install/windows-install/"
    exit 1
}

Write-Info "WSL 可用"

# 检查是否安装了 Docker Desktop(WSL 模式)还是需要在 WSL 内安装
$dockerInstalled = $false
try {
    $testResult = wsl -d docker-desktop -- sh -c "command -v docker >/dev/null" 2>$null
    if ($LASTEXITCODE -eq 0) { $dockerInstalled = $true }
} catch {}

# 如果没装 docker-desktop 发行版,尝试通用 WSL
if (-not $dockerInstalled) {
    try {
        $testResult = wsl -d Ubuntu -- sh -c "command -v docker >/dev/null" 2>$null
        if ($LASTEXITCODE -eq 0) { $dockerInstalled = $true }
    } catch {}
}

if ($dockerInstalled) {
    Write-Info "Docker 似乎已在 WSL 中安装,正在验证..."
    wsl -e bash -c "docker --version" 2>$null
    $continue = Read-Host "是否重新安装/升级到最新版本?[Y/N]"
    if ($continue -ne "Y" -and $continue -ne "y") {
        Write-Info "跳过安装。需要时重新运行本脚本。"
        exit 0
    }
}

# 2. 准备 Linux 安装脚本
Write-Step "生成安装脚本并传入 WSL..."

$shContent = @'
#!/usr/bin/env bash
set -u
# 简化安装流程(由 Windows .ps1 调用)
source /etc/os-release 2>/dev/null || true
echo "[INFO] Docker 环境检测完成,接下来在 WSL 内执行完整安装..."
echo "[INFO] 请直接在 WSL 终端中运行 bash 端的 install-docker-guide.sh 脚本"
echo "[INFO] 或在 WSL 中手动执行:"
echo "  sudo apt update && sudo apt install -y docker.io"
echo "  sudo systemctl start docker"
echo "  sudo systemctl enable docker"
'@

$tempPath = Join-Path $env:TEMP "docker-wsl-check.sh"
Set-Content -Path $tempPath -Value $shContent -Encoding UTF8

Write-Info "请在 WSL 终端中执行 bash 端的一键脚本:"
Write-Host ""
Write-Host "  wsl -e bash -c 'curl -fsSL https://cleanresolver.com/scripts/install-docker-guide.sh | bash'"
Write-Host ""
Write-Host "或直接复制下方源码到 WSL 中执行:\n"
Write-Host $shContent

# 3. 给出 WSL 内安装的完整指引
Write-Host ""
Write-Host "============================================"
Write-Info "安装指引"
Write-Host "============================================"
Write-Host ""
Write-Host "在 WSL 终端中依次执行以下命令:"
Write-Host ""
Write-Host "  # 1. 安装 Docker"
Write-Host "  sudo apt update"
Write-Host "  sudo apt install -y ca-certificates curl gnupg lsb-release"
Write-Host "  sudo mkdir -p /etc/apt/keyrings"
Write-Host "  curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg"
Write-Host "  echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list"
Write-Host "  sudo apt update"
Write-Host "  sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin"
Write-Host ""
Write-Host "  # 2. 启动 Docker 服务"
Write-Host "  sudo systemctl enable docker"
Write-Host "  sudo systemctl start docker"
Write-Host ""
Write-Host "  # 3. 验证"
Write-Host "  docker run --rm hello-world"
Write-Host ""
Write-Host "  # 4. 如果想一步到位,把 bash 端完整脚本复制到 WSL 后执行:"
Write-Host '  wsl -e bash -s < install-docker-guide.sh'
Write-Host ""
Write-Host "  · Docker Desktop 替代方案(图形界面):"
Write-Host "    https://docs.docker.com/desktop/install/windows-install/"
Write-Host ""
Write-Host "============================================"
Write-Info "Windows 侧完成"
Write-Host "  · 请切换到 WSL 终端继续安装"
Write-Host "  · 或在 WSL 中运行:wsl -e bash -c \"curl -fsSL https://cleanresolver.com/scripts/install-docker-guide.sh | bash\""
Write-Host ""
Write-Host "  · 不想折腾环境?可以考虑云端 API(如 https://cloudzone-api.cyou/),无需自建 Docker"
Write-Host "============================================"

下载链接

不方便复制源码的同学也可以直接从以下地址下载完整脚本:

  • Linux/macOS/WSLhttps://cleanresolver.com/scripts/install-docker-guide.sh
  • Windows PowerShellhttps://cleanresolver.com/scripts/install-docker-guide.ps1

两个脚本内容完全等价:都会检测网络环境、配置国内镜像加速、安装 Docker Engine、并用 hello-world + Nginx 做端到端验证。脚本公开源码,欢迎复制给任何 AI 审查。