Docker 从入门到进阶:一篇写给工程师的系统化实战指南

1. Docker 到底解决什么问题

在没有容器之前,一个应用从开发机走到测试、预发、生产,经常会遇到这些问题:

  • 本地 Node、Java、Python、系统库版本和服务器不一致。
  • 部署文档写了十几步,少执行一步就出错。
  • 应用依赖数据库、缓存、消息队列,多人协作时环境难以复现。
  • 回滚不稳定,因为机器上的包、配置和临时文件已经被改过很多次。

Docker 的核心价值是把"应用如何运行"标准化。它把应用代码、系统依赖、运行命令、端口、环境变量等打包成镜像,再用镜像启动容器。镜像是可分发的模板,容器是运行中的实例。

需要注意:Docker 不是传统意义上的虚拟机。Linux 上的容器共享宿主机内核,通过命名空间、控制组、联合文件系统等机制实现隔离和资源控制。macOS 和 Windows 上运行 Linux 容器时,Docker Desktop 通常会在背后使用轻量 Linux 虚拟机承载 Docker Engine,所以不要把"容器"和"完整虚拟机"混为一谈。

一个简单但非常重要的心智模型:

  • Dockerfile:描述如何构建镜像的文本文件。
  • Image 镜像:只读模板,包含应用与依赖,通常按层保存。
  • Container 容器:镜像启动后的运行实例,拥有自己的可写层、进程、网络和挂载。
  • Registry 镜像仓库:存储和分发镜像,例如 Docker Hub、私有 Harbor、云厂商容器镜像仓库。
  • Volume 卷:用于保存容器之外仍需保留的数据。
  • Network 网络:让容器互相访问,也让外部流量进入容器。

2. 安装后的第一组命令

安装 Docker Desktop 或 Docker Engine 后,先确认命令可用:

bash 复制代码
docker version
docker info
docker compose version

其中 docker compose 是现在推荐的 Compose V2 调用方式。老文章中常见的 docker-compose 是历史命令,新项目建议统一使用 docker compose

用 Nginx 跑一个最小示例:

bash 复制代码
docker pull nginx:alpine
docker run -d -p 8080:80 --name web nginx:alpine
docker ps

打开浏览器访问:

text 复制代码
http://localhost:8080

这条命令中最重要的参数是:

  • -d:后台运行容器。
  • -p 8080:80:把宿主机的 8080 端口映射到容器内的 80 端口。
  • --name web:给容器起名,后续用名字管理更方便。
  • nginx:alpine:镜像名与标签。alpine 表示这个镜像基于 Alpine Linux 变体。

查看日志、进入容器、停止并删除:

bash 复制代码
docker logs -f web
docker exec -it web sh
docker stop web
docker rm web

如果只是临时体验,可以使用 --rm,容器退出后自动删除:

bash 复制代码
docker run --rm alpine:3.20 echo "hello docker"

常用对象查看命令:

bash 复制代码
docker ps              # 查看运行中的容器
docker ps -a           # 查看全部容器
docker images          # 查看本地镜像
docker volume ls       # 查看卷
docker network ls      # 查看网络
docker system df       # 查看 Docker 占用空间

清理命令要谨慎:

bash 复制代码
docker container prune # 删除已停止容器
docker image prune     # 删除悬空镜像
docker volume prune    # 删除未使用卷,可能清掉数据
docker system prune    # 综合清理,发布机上务必先确认影响

3. 镜像、容器与仓库:不要把三个概念混在一起

镜像是只读模板,容器是在镜像之上增加可写层后的运行实例。删除容器不会删除镜像;删除镜像也不应该影响已经基于该镜像创建出的容器文件系统,但如果容器仍引用镜像,Docker 通常会阻止你删除该镜像。

3.1 镜像标签不是版本锁

很多人误以为 latest 表示"永远最新",这是一个危险误解。latest 只是一个普通标签,镜像作者可以随时让它指向不同内容。生产环境建议使用明确版本:

bash 复制代码
docker run nginx:1.27-alpine
docker run postgres:16

对强一致性要求高的场景,可以使用镜像摘要:

bash 复制代码
docker run nginx@sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

摘要能更严格地锁定内容,但会增加升级维护成本。实际工程中常见做法是:开发环境用语义化标签,生产发布系统记录标签、摘要、Git commit 和构建时间。

3.2 镜像分层为什么重要

Dockerfile 中每个关键构建步骤会形成镜像层。层可以缓存和复用,所以 Dockerfile 的顺序会直接影响构建速度:

dockerfile 复制代码
# 不推荐:代码一变,依赖安装层也容易失效
COPY . .
RUN npm ci

# 推荐:先复制依赖清单,再安装依赖,最后复制业务代码
COPY package*.json ./
RUN npm ci
COPY . .

这背后的原则是:变化少的步骤放前面,变化频繁的业务代码放后面。

4. 数据持久化:容器可以删,数据不能丢

容器默认文件系统适合放临时运行状态,不适合放长期业务数据。数据库文件、上传文件、队列状态、缓存快照等需要明确挂载。

4.1 Bind Mount:适合开发热更新

Bind Mount 把宿主机某个目录直接挂进容器,适合开发时同步代码:

bash 复制代码
docker run --rm -it \
  --mount type=bind,source="$PWD",target=/app \
  -w /app node:22-alpine sh

优点是直观,宿主机改文件,容器里立刻看到。缺点是依赖宿主机路径和权限,跨平台团队要小心 Windows、macOS、Linux 的路径差异。

4.2 Named Volume:适合数据库和长期数据

命名卷由 Docker 管理,适合数据库数据:

bash 复制代码
docker volume create pg-data

docker run -d \
  --name pg \
  -e POSTGRES_PASSWORD=example \
  -v pg-data:/var/lib/postgresql/data \
  postgres:16

这里的 example 只用于本地演示。生产环境不要把真实密码直接写进命令、脚本或仓库,应使用 secrets 或平台密钥管理能力。

删除容器不会删除命名卷:

bash 复制代码
docker rm -f pg
docker volume ls

但下面这类命令可能删除未使用卷,生产环境不要随手执行:

bash 复制代码
docker volume prune
docker compose down -v

4.3 tmpfs:适合敏感临时文件

tmpfs 把数据放在内存中,容器停止后数据消失,适合临时缓存、敏感中间文件:

bash 复制代码
docker run --rm \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  alpine:3.20 sh -c "df -h /tmp"

5. 网络与端口:服务名优先,IP 靠后

Docker 网络里最容易混淆的是"容器内部端口"和"宿主机端口":

  • 容器内部端口:应用在容器里监听的端口,例如 Nginx 监听 80。
  • 宿主机端口:外部用户访问宿主机时使用的端口,例如 localhost:8080
  • -p 8080:80:左边是宿主机端口,右边是容器端口。

创建一个用户自定义网络:

bash 复制代码
docker network create demo-net

docker run -d --name api --network demo-net my-api:dev
docker run --rm --network demo-net alpine:3.20 \
  sh -c "wget -qO- http://api:3000/health"

同一 Docker 网络内,容器可以用容器名或 Compose 服务名互相访问。不要在应用配置里硬编码容器 IP,因为容器重建后 IP 可能变化。

几个常见规则:

  • EXPOSE 不等于发布端口,它更像镜像元数据和文档。
  • ports-p 才会把容器端口发布到宿主机。
  • Compose 中同一项目默认会创建一个网络,服务可通过服务名互访。
  • 对内服务尽量只放在 Docker 网络里,不要无必要地发布到公网或宿主机所有网卡。

6. Docker Compose:把多容器应用写成蓝图

当应用依赖数据库、缓存、队列、对象存储模拟器时,单条 docker run 很快会变得难维护。Compose 的价值是把多容器应用写进一个声明式 YAML 文件。

新项目建议文件名使用 compose.yaml。Compose Specification 已经是滚动规范,旧式顶层 version: 对新项目通常没有必要,写了反而容易让读者误以为这是 Compose 文件格式版本锁。

下面是一个 API + PostgreSQL + Redis 的开发环境示例:

yaml 复制代码
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development
      DATABASE_URL: postgres://app:example@db:5432/app
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - .:/app
      - api-node-modules:/app/node_modules

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: example
      POSTGRES_DB: app
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine

volumes:
  db-data:
  api-node-modules:

上面的 POSTGRES_PASSWORD: example 只适合本地开发演示。团队环境和生产环境应改用 secrets、密钥管理服务或部署平台提供的安全注入方式。

启动和查看:

bash 复制代码
docker compose up -d --build
docker compose ps
docker compose logs -f api
docker compose exec api sh

停止:

bash 复制代码
docker compose down

重点提醒:

  • depends_on 可以控制启动顺序,配合健康检查可以等待依赖达到健康状态;但应用代码仍应具备重试数据库、缓存等依赖的能力。
  • docker compose down 默认会删除 Compose 创建的容器和网络;加上 -v 会删除卷,可能造成数据丢失。
  • 开发环境可以挂载源码,生产镜像不建议依赖源码 Bind Mount。

6.1 Compose 中使用 secrets

密码、令牌、私钥不要写进镜像,也不要直接写进 Git 仓库。Compose 可以把 secret 作为文件挂载到容器:

yaml 复制代码
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

这里环境变量里只写 secret 文件路径,真正密码来自容器内的 /run/secrets/db_password

7. 写一个专业的 Dockerfile

Dockerfile 不是"把本机命令搬进去"这么简单。一个好的 Dockerfile 应该具备这些特征:

  • 可重复构建:同样输入尽量得到同样输出。
  • 构建快:合理利用缓存。
  • 镜像小:最终镜像只包含运行必需文件。
  • 安全:不把密钥、源码历史、构建工具链带进运行镜像。
  • 可运维:提供健康检查、明确端口、清晰启动命令。

下面是一个 Node.js API 的多阶段构建模板。它不是唯一答案,但体现了工程上常用的结构:

dockerfile 复制代码
# syntax=docker/dockerfile:1

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM deps AS build
WORKDIR /app
COPY . .
RUN npm run build

FROM node:22-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package*.json ./

USER node
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD node -e "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"

CMD ["node", "dist/server.js"]

几个关键点:

  • 第一行 # syntax=docker/dockerfile:1 让你可以使用较新的 Dockerfile 语法能力。
  • deps 阶段只安装依赖,便于缓存。
  • build 阶段负责构建产物。
  • runtime 阶段只复制运行需要的文件,不包含完整源码和构建缓存。
  • USER node 避免应用以 root 用户运行。前提是应用不需要写入没有权限的目录,日志应输出到标准输出。
  • EXPOSE 3000 只是声明容器内服务端口,不会自动把端口发布到宿主机。真正发布端口仍要用 -p 或 Compose 的 ports

7.1 一定要写 .dockerignore

.dockerignore 用来控制构建上下文。如果不写,node_modules、日志、测试缓存、.git、本地密钥都可能被发送给 Docker 构建器,拖慢构建甚至造成泄漏。

示例:

gitignore 复制代码
.git
node_modules
dist
coverage
.env
.env.*
*.log
Dockerfile*
compose*.yaml
README.md

注意:是否忽略 distcompose*.yamlREADME.md 要根据项目情况决定。如果构建阶段需要这些文件,就不能忽略。

7.2 构建、运行和验证

bash 复制代码
docker build -t my-api:dev .
docker run --rm -p 3000:3000 --name my-api my-api:dev
docker logs -f my-api

查看镜像层和元数据:

bash 复制代码
docker image inspect my-api:dev
docker history my-api:dev

8. 进阶构建:BuildKit、密钥和多平台镜像

8.1 构建时不要用 ARG 或 ENV 传密钥

构建参数和环境变量不适合传递密钥,因为它们可能出现在镜像历史、构建记录或最终镜像元数据中。构建时需要访问私有包仓库、私有 Git 仓库、云凭证时,应该使用 Build secrets 或 SSH mount。

示例:构建时临时挂载 .npmrc

bash 复制代码
docker build \
  --secret id=npmrc,src=.npmrc \
  -t my-api:secure .

Dockerfile:

dockerfile 复制代码
# syntax=docker/dockerfile:1

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

这样 .npmrc 不会被复制进镜像层。

8.2 构建多平台镜像

如果你的镜像要同时跑在 x86 服务器和 ARM 机器上,可以用 buildx 构建多平台镜像:

bash 复制代码
docker buildx create --name multiarch --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t registry.example.com/my-api:1.2.3 \
  --push .

多平台构建常见于:

  • 云服务器与本地 Apple Silicon 开发机混用。
  • 边缘设备、树莓派、ARM 节点需要同一应用镜像。
  • 团队希望一个标签支持多个 CPU 架构。

8.3 发布标签建议

推荐同时记录:

  • 语义化版本:1.2.3
  • Git commit:git-abc1234
  • 环境标签:stagingproduction
  • 镜像摘要:sha256:...

不要只依赖 latestlatest 可以用于演示或本地体验,但生产发布应该能追溯到具体源码和构建产物。

9. 生产环境检查清单

上线前至少检查以下内容。

9.1 安全

  • 使用可信基础镜像,优先选择官方镜像或团队维护的基础镜像。
  • 不在 Dockerfile、镜像层、环境变量、仓库中硬编码密钥。
  • 尽量使用非 root 用户运行应用。
  • 不把 Docker socket 随意挂进容器。/var/run/docker.sock 等同于授予很高的宿主机控制能力。
  • 对镜像做漏洞扫描,并建立升级基础镜像的节奏。
  • 生产容器尽量只开放必要端口。

9.2 稳定性

  • 为服务提供健康检查。
  • 设置合理的重启策略,例如 restart: unless-stopped
  • 为关键服务配置 CPU、内存限制,并观察真实峰值。
  • 应用要能处理 SIGTERM,实现优雅停止。
  • 不要把业务数据只放在容器可写层。

示例:

yaml 复制代码
services:
  api:
    image: registry.example.com/my-api:1.2.3
    restart: unless-stopped
    ports:
      - "3000:3000"
    read_only: true
    tmpfs:
      - /tmp:size=64m
    mem_limit: 512m
    cpus: 1.0

是否能启用 read_onlycap_droptmpfs,取决于应用是否需要写本地文件、绑定低端口、调用系统能力。安全配置不能机械套用,必须压测和验证。

9.3 可观测

  • 日志输出到标准输出和标准错误,由平台采集。
  • 健康检查接口不要依赖过重逻辑,避免把数据库慢查询变成健康检查雪崩。
  • 指标至少包含请求量、错误率、延迟、资源使用、依赖连接状态。
  • 保留镜像标签、摘要、构建时间、commit,便于回滚和审计。

9.4 数据

  • 数据库存储使用命名卷、云盘或外部托管服务。
  • 建立备份策略,并定期做恢复演练。
  • 明确哪些数据随容器删除,哪些数据随卷保留。
  • 谨慎使用 docker compose down -vdocker volume prune

10. 故障排查手册

10.1 先看容器是否在运行

bash 复制代码
docker ps
docker ps -a
docker inspect <container>

重点看:

  • Status
  • ExitCode
  • RestartCount
  • Mounts
  • NetworkSettings
  • Health

10.2 再看日志

bash 复制代码
docker logs --tail=200 <container>
docker logs -f <container>
docker compose logs -f api

如果日志为空,通常有几种可能:

  • 应用根本没有启动到日志初始化阶段。
  • 应用把日志写到文件而不是标准输出。
  • 容器启动命令错了,进程直接退出。

10.3 排查端口

bash 复制代码
docker port <container>
docker inspect <container> --format '{{json .NetworkSettings.Ports}}'

常见错误:

  • -p 80:8080-p 8080:80 写反。
  • 宿主机端口已经被占用。
  • 应用只监听 127.0.0.1,没有监听容器内的 0.0.0.0
  • 只写了 EXPOSE,却没有真正发布端口。

10.4 排查网络

bash 复制代码
docker network ls
docker network inspect <network>
docker compose exec api sh

进入容器后检查:

bash 复制代码
getent hosts db
nc -vz db 5432
wget -qO- http://api:3000/health

不同镜像内置工具不同。Alpine 里可能需要安装 busybox-extras 才有 nc。生产镜像为了保持精简,通常不会内置太多排查工具,可以临时启动一个同网络的调试容器:

bash 复制代码
docker run --rm -it --network <network> alpine:3.20 sh

10.5 排查资源

bash 复制代码
docker stats
docker events
docker system df

如果容器频繁退出,关注:

  • 是否 OOM。
  • 是否健康检查失败后被平台重启。
  • 是否启动命令使用了前台进程。容器里的主进程退出,容器就会退出。
  • 是否磁盘写满或日志无限增长。

10.6 常见问题速查

现象 高概率原因 排查方向
浏览器访问不到服务 端口映射错误、服务未监听 0.0.0.0、宿主机防火墙 docker psdocker port、应用监听地址
容器一启动就退出 主进程退出、命令写错、缺环境变量 docker logsdocker inspectExitCode
API 访问不到数据库 不在同一网络、服务名写错、数据库未就绪 Compose 服务名、健康检查、应用重试
数据库数据丢失 数据写在容器层、执行了 down -vvolume prune 查看 volumes 配置与备份
镜像太大 构建工具链进入运行镜像、上下文过大、没有 .dockerignore 多阶段构建、docker history
构建很慢 缓存顺序不合理、上下文包含大目录 先复制依赖清单、优化 .dockerignore
权限错误 非 root 用户无法写目录、挂载目录 UID/GID 不匹配 USER、目录权限、卷权限

11. 一套推荐学习路线

如果你刚开始学 Docker,建议按这个顺序练习:

  1. docker run 跑通 Nginx、Redis、PostgreSQL。
  2. 学会 pslogsexecinspectstoprm
  3. 给自己的一个小项目写 Dockerfile。
  4. 使用 .dockerignore 和多阶段构建优化镜像。
  5. 用 Volume 保存数据库数据。
  6. 用 Docker Network 或 Compose 服务名连接 API、数据库、缓存。
  7. compose.yaml 管理完整开发环境。
  8. 学 Build secrets,不再把密钥写进镜像。
  9. buildx,构建多平台镜像。
  10. 用生产检查清单复盘安全、备份、健康检查和可观测性。

12. 最终最佳实践清单

  • 新项目使用 docker compose,不要继续传播旧式 docker-compose 作为默认命令。
  • 新项目的 Compose 文件通常不需要顶层 version:
  • 生产镜像不要依赖 latest,要使用明确版本,并记录镜像摘要。
  • Dockerfile 中变化少的步骤放前面,业务代码放后面。
  • 使用 .dockerignore 减少构建上下文。
  • 使用多阶段构建,最终镜像只保留运行必需文件。
  • 不用 ARGENV、代码仓库传递密钥,构建时使用 Build secrets,运行时使用 secrets 或平台密钥管理。
  • 容器内应用监听 0.0.0.0,外部访问通过 -p 或 Compose ports 发布。
  • 服务间通信优先用服务名,不硬编码容器 IP。
  • 数据库等长期数据使用命名卷或外部持久化服务。
  • 上线前验证健康检查、重启策略、资源限制、日志采集和备份恢复。
  • 不随意执行 docker volume prunedocker system prunedocker compose down -v
  • 不随意挂载 Docker socket 到业务容器。

13. 参考资料

  • Docker Docs
相关推荐
梦想的旅途22 小时前
利用 API 实现企业微信消息自动化推送
运维·自动化·企业微信
Acrellea2 小时前
固态变压器(SST)热管理破局:安科瑞交直流专属测温方案护航无人值守运维
运维·网络
zjun30212 小时前
如何搭建TRAE IDE 连接到容器开发环境
docker·容器·ascend·trae
寒冰碧海2 小时前
大模型部署从0到1(一):通用环境全流程准备|NVIDIA驱动+Docker+NVIDIA Container Toolkit
运维·docker·容器
风曦Kisaki2 小时前
# Linux 系统资源查看命令总结(附命令快查表)
linux·运维·服务器
hj2862512 小时前
K8S 核心组件与资源概念 + 带注释命令 + 实操案例版笔记
笔记·容器·kubernetes
蝶恋舞者3 小时前
DNS 服务器(后续持续更新)
运维·服务器
Mortalbreeze3 小时前
深入理解 Linux 线程机制(四):线程同步——条件变量与信号量
linux·运维·服务器·开发语言·c++
nece0014 小时前
Kubernetes v1.36.0 + Ubuntu24.04 + containerd 2.x WordPress 完整部署文档
云原生·容器·kubernetes