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

发布时间:2026/7/18 21:34:16
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 后先确认命令可用dockerversiondockerinfodockercompose version其中docker compose是现在推荐的 Compose V2 调用方式。老文章中常见的docker-compose是历史命令新项目建议统一使用docker compose。用 Nginx 跑一个最小示例dockerpull nginx:alpinedockerrun-d-p8080:80--nameweb nginx:alpinedockerps打开浏览器访问http://localhost:8080这条命令中最重要的参数是-d后台运行容器。-p 8080:80把宿主机的 8080 端口映射到容器内的 80 端口。--name web给容器起名后续用名字管理更方便。nginx:alpine镜像名与标签。alpine表示这个镜像基于 Alpine Linux 变体。查看日志、进入容器、停止并删除dockerlogs-fwebdockerexec-itwebshdockerstop webdockerrmweb如果只是临时体验可以使用--rm容器退出后自动删除dockerrun--rmalpine:3.20echohello docker常用对象查看命令dockerps# 查看运行中的容器dockerps-a# 查看全部容器dockerimages# 查看本地镜像dockervolumels# 查看卷dockernetworkls# 查看网络dockersystemdf# 查看 Docker 占用空间清理命令要谨慎dockercontainer prune# 删除已停止容器dockerimage prune# 删除悬空镜像dockervolume prune# 删除未使用卷可能清掉数据dockersystem prune# 综合清理发布机上务必先确认影响3. 镜像、容器与仓库不要把三个概念混在一起镜像是只读模板容器是在镜像之上增加可写层后的运行实例。删除容器不会删除镜像删除镜像也不应该影响已经基于该镜像创建出的容器文件系统但如果容器仍引用镜像Docker 通常会阻止你删除该镜像。3.1 镜像标签不是版本锁很多人误以为latest表示“永远最新”这是一个危险误解。latest只是一个普通标签镜像作者可以随时让它指向不同内容。生产环境建议使用明确版本dockerrun nginx:1.27-alpinedockerrun postgres:16对强一致性要求高的场景可以使用镜像摘要dockerrun nginxsha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx摘要能更严格地锁定内容但会增加升级维护成本。实际工程中常见做法是开发环境用语义化标签生产发布系统记录标签、摘要、Git commit 和构建时间。3.2 镜像分层为什么重要Dockerfile 中每个关键构建步骤会形成镜像层。层可以缓存和复用所以 Dockerfile 的顺序会直接影响构建速度# 不推荐代码一变依赖安装层也容易失效 COPY . . RUN npm ci # 推荐先复制依赖清单再安装依赖最后复制业务代码 COPY package*.json ./ RUN npm ci COPY . .这背后的原则是变化少的步骤放前面变化频繁的业务代码放后面。4. 数据持久化容器可以删数据不能丢容器默认文件系统适合放临时运行状态不适合放长期业务数据。数据库文件、上传文件、队列状态、缓存快照等需要明确挂载。4.1 Bind Mount适合开发热更新Bind Mount 把宿主机某个目录直接挂进容器适合开发时同步代码dockerrun--rm-it\--mounttypebind,source$PWD,target/app\-w/app node:22-alpinesh优点是直观宿主机改文件容器里立刻看到。缺点是依赖宿主机路径和权限跨平台团队要小心 Windows、macOS、Linux 的路径差异。4.2 Named Volume适合数据库和长期数据命名卷由 Docker 管理适合数据库数据dockervolume create pg-datadockerrun-d\--namepg\-ePOSTGRES_PASSWORDexample\-vpg-data:/var/lib/postgresql/data\postgres:16这里的example只用于本地演示。生产环境不要把真实密码直接写进命令、脚本或仓库应使用 secrets 或平台密钥管理能力。删除容器不会删除命名卷dockerrm-fpgdockervolumels但下面这类命令可能删除未使用卷生产环境不要随手执行dockervolume prunedockercompose down-v4.3 tmpfs适合敏感临时文件tmpfs把数据放在内存中容器停止后数据消失适合临时缓存、敏感中间文件dockerrun--rm\--tmpfs/tmp:rw,noexec,nosuid,size64m\alpine:3.20sh-cdf -h /tmp5. 网络与端口服务名优先IP 靠后Docker 网络里最容易混淆的是“容器内部端口”和“宿主机端口”容器内部端口应用在容器里监听的端口例如 Nginx 监听 80。宿主机端口外部用户访问宿主机时使用的端口例如localhost:8080。-p 8080:80左边是宿主机端口右边是容器端口。创建一个用户自定义网络dockernetwork create demo-netdockerrun-d--nameapi--networkdemo-net my-api:devdockerrun--rm--networkdemo-net alpine:3.20\sh-cwget -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 的开发环境示例services:api:build:context:.dockerfile:Dockerfileports:-3000:3000environment:NODE_ENV:developmentDATABASE_URL:postgres://app:exampledb:5432/appREDIS_URL:redis://redis:6379depends_on:db:condition:service_healthyredis:condition:service_startedvolumes:-.:/app-api-node-modules:/app/node_modulesdb:image:postgres:16environment:POSTGRES_USER:appPOSTGRES_PASSWORD:examplePOSTGRES_DB:appvolumes:-db-data:/var/lib/postgresql/datahealthcheck:test:[CMD-SHELL,pg_isready -U app -d app]interval:10stimeout:5sretries:5redis:image:redis:7-alpinevolumes:db-data:api-node-modules:上面的POSTGRES_PASSWORD: example只适合本地开发演示。团队环境和生产环境应改用 secrets、密钥管理服务或部署平台提供的安全注入方式。启动和查看dockercompose up-d--builddockercomposepsdockercompose logs-fapidockercomposeexecapish停止dockercompose down重点提醒depends_on可以控制启动顺序配合健康检查可以等待依赖达到健康状态但应用代码仍应具备重试数据库、缓存等依赖的能力。docker compose down默认会删除 Compose 创建的容器和网络加上-v会删除卷可能造成数据丢失。开发环境可以挂载源码生产镜像不建议依赖源码 Bind Mount。6.1 Compose 中使用 secrets密码、令牌、私钥不要写进镜像也不要直接写进 Git 仓库。Compose 可以把 secret 作为文件挂载到容器services:db:image:postgres:16environment:POSTGRES_USER:appPOSTGRES_DB:appPOSTGRES_PASSWORD_FILE:/run/secrets/db_passwordsecrets:-db_passwordsecrets:db_password:file:./secrets/db_password.txt这里环境变量里只写 secret 文件路径真正密码来自容器内的/run/secrets/db_password。7. 写一个专业的 DockerfileDockerfile 不是“把本机命令搬进去”这么简单。一个好的 Dockerfile 应该具备这些特征可重复构建同样输入尽量得到同样输出。构建快合理利用缓存。镜像小最终镜像只包含运行必需文件。安全不把密钥、源码历史、构建工具链带进运行镜像。可运维提供健康检查、明确端口、清晰启动命令。下面是一个 Node.js API 的多阶段构建模板。它不是唯一答案但体现了工程上常用的结构# syntaxdocker/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_ENVproduction WORKDIR /app COPY --fromdeps /app/node_modules ./node_modules COPY --frombuild /app/dist ./dist COPY package*.json ./ USER node EXPOSE 3000 HEALTHCHECK --interval30s --timeout5s --retries3 \ CMD node -e fetch(http://127.0.0.1:3000/health).then(rprocess.exit(r.ok?0:1)).catch(()process.exit(1)) CMD [node, dist/server.js]几个关键点第一行# syntaxdocker/dockerfile:1让你可以使用较新的 Dockerfile 语法能力。deps阶段只安装依赖便于缓存。build阶段负责构建产物。runtime阶段只复制运行需要的文件不包含完整源码和构建缓存。USER node避免应用以 root 用户运行。前提是应用不需要写入没有权限的目录日志应输出到标准输出。EXPOSE 3000只是声明容器内服务端口不会自动把端口发布到宿主机。真正发布端口仍要用-p或 Compose 的ports。7.1 一定要写.dockerignore.dockerignore用来控制构建上下文。如果不写node_modules、日志、测试缓存、.git、本地密钥都可能被发送给 Docker 构建器拖慢构建甚至造成泄漏。示例.git node_modules dist coverage .env .env.* *.log Dockerfile* compose*.yaml README.md注意是否忽略dist、compose*.yaml、README.md要根据项目情况决定。如果构建阶段需要这些文件就不能忽略。7.2 构建、运行和验证dockerbuild-tmy-api:dev.dockerrun--rm-p3000:3000--namemy-api my-api:devdockerlogs-fmy-api查看镜像层和元数据dockerimage inspect my-api:devdockerhistorymy-api:dev8. 进阶构建BuildKit、密钥和多平台镜像8.1 构建时不要用 ARG 或 ENV 传密钥构建参数和环境变量不适合传递密钥因为它们可能出现在镜像历史、构建记录或最终镜像元数据中。构建时需要访问私有包仓库、私有 Git 仓库、云凭证时应该使用 Build secrets 或 SSH mount。示例构建时临时挂载.npmrcdockerbuild\--secretidnpmrc,src.npmrc\-tmy-api:secure.Dockerfile# syntaxdocker/dockerfile:1 FROM node:22-alpine AS deps WORKDIR /app COPY package*.json ./ RUN --mounttypesecret,idnpmrc,target/root/.npmrc npm ci这样.npmrc不会被复制进镜像层。8.2 构建多平台镜像如果你的镜像要同时跑在 x86 服务器和 ARM 机器上可以用buildx构建多平台镜像dockerbuildx create--namemultiarch--usedockerbuildx build\--platformlinux/amd64,linux/arm64\-tregistry.example.com/my-api:1.2.3\--push.多平台构建常见于云服务器与本地 Apple Silicon 开发机混用。边缘设备、树莓派、ARM 节点需要同一应用镜像。团队希望一个标签支持多个 CPU 架构。8.3 发布标签建议推荐同时记录语义化版本1.2.3Git commitgit-abc1234环境标签staging、production镜像摘要sha256:...不要只依赖latest。latest可以用于演示或本地体验但生产发布应该能追溯到具体源码和构建产物。9. 生产环境检查清单上线前至少检查以下内容。9.1 安全使用可信基础镜像优先选择官方镜像或团队维护的基础镜像。不在 Dockerfile、镜像层、环境变量、仓库中硬编码密钥。尽量使用非 root 用户运行应用。不把 Docker socket 随意挂进容器。/var/run/docker.sock等同于授予很高的宿主机控制能力。对镜像做漏洞扫描并建立升级基础镜像的节奏。生产容器尽量只开放必要端口。9.2 稳定性为服务提供健康检查。设置合理的重启策略例如restart: unless-stopped。为关键服务配置 CPU、内存限制并观察真实峰值。应用要能处理 SIGTERM实现优雅停止。不要把业务数据只放在容器可写层。示例services:api:image:registry.example.com/my-api:1.2.3restart:unless-stoppedports:-3000:3000read_only:truetmpfs:-/tmp:size64mmem_limit:512mcpus:1.0是否能启用read_only、cap_drop、tmpfs取决于应用是否需要写本地文件、绑定低端口、调用系统能力。安全配置不能机械套用必须压测和验证。9.3 可观测日志输出到标准输出和标准错误由平台采集。健康检查接口不要依赖过重逻辑避免把数据库慢查询变成健康检查雪崩。指标至少包含请求量、错误率、延迟、资源使用、依赖连接状态。保留镜像标签、摘要、构建时间、commit便于回滚和审计。9.4 数据数据库存储使用命名卷、云盘或外部托管服务。建立备份策略并定期做恢复演练。明确哪些数据随容器删除哪些数据随卷保留。谨慎使用docker compose down -v、docker volume prune。10. 故障排查手册10.1 先看容器是否在运行dockerpsdockerps-adockerinspectcontainer重点看StatusExitCodeRestartCountMountsNetworkSettingsHealth10.2 再看日志dockerlogs--tail200containerdockerlogs-fcontainerdockercompose logs-fapi如果日志为空通常有几种可能应用根本没有启动到日志初始化阶段。应用把日志写到文件而不是标准输出。容器启动命令错了进程直接退出。10.3 排查端口dockerportcontainerdockerinspectcontainer--format{{json .NetworkSettings.Ports}}常见错误把-p 80:8080和-p 8080:80写反。宿主机端口已经被占用。应用只监听127.0.0.1没有监听容器内的0.0.0.0。只写了EXPOSE却没有真正发布端口。10.4 排查网络dockernetworklsdockernetwork inspectnetworkdockercomposeexecapish进入容器后检查getent hosts dbnc-vzdb5432wget-qO- http://api:3000/health不同镜像内置工具不同。Alpine 里可能需要安装busybox-extras才有nc。生产镜像为了保持精简通常不会内置太多排查工具可以临时启动一个同网络的调试容器dockerrun--rm-it--networknetworkalpine:3.20sh10.5 排查资源dockerstatsdockereventsdockersystemdf如果容器频繁退出关注是否 OOM。是否健康检查失败后被平台重启。是否启动命令使用了前台进程。容器里的主进程退出容器就会退出。是否磁盘写满或日志无限增长。10.6 常见问题速查现象高概率原因排查方向浏览器访问不到服务端口映射错误、服务未监听0.0.0.0、宿主机防火墙docker ps、docker port、应用监听地址容器一启动就退出主进程退出、命令写错、缺环境变量docker logs、docker inspect的ExitCodeAPI 访问不到数据库不在同一网络、服务名写错、数据库未就绪Compose 服务名、健康检查、应用重试数据库数据丢失数据写在容器层、执行了down -v或volume prune查看volumes配置与备份镜像太大构建工具链进入运行镜像、上下文过大、没有.dockerignore多阶段构建、docker history构建很慢缓存顺序不合理、上下文包含大目录先复制依赖清单、优化.dockerignore权限错误非 root 用户无法写目录、挂载目录 UID/GID 不匹配USER、目录权限、卷权限11. 一套推荐学习路线如果你刚开始学 Docker建议按这个顺序练习用docker run跑通 Nginx、Redis、PostgreSQL。学会ps、logs、exec、inspect、stop、rm。给自己的一个小项目写 Dockerfile。使用.dockerignore和多阶段构建优化镜像。用 Volume 保存数据库数据。用 Docker Network 或 Compose 服务名连接 API、数据库、缓存。写compose.yaml管理完整开发环境。学 Build secrets不再把密钥写进镜像。学buildx构建多平台镜像。用生产检查清单复盘安全、备份、健康检查和可观测性。12. 最终最佳实践清单新项目使用docker compose不要继续传播旧式docker-compose作为默认命令。新项目的 Compose 文件通常不需要顶层version:。生产镜像不要依赖latest要使用明确版本并记录镜像摘要。Dockerfile 中变化少的步骤放前面业务代码放后面。使用.dockerignore减少构建上下文。使用多阶段构建最终镜像只保留运行必需文件。不用ARG、ENV、代码仓库传递密钥构建时使用 Build secrets运行时使用 secrets 或平台密钥管理。容器内应用监听0.0.0.0外部访问通过-p或 Composeports发布。服务间通信优先用服务名不硬编码容器 IP。数据库等长期数据使用命名卷或外部持久化服务。上线前验证健康检查、重启策略、资源限制、日志采集和备份恢复。不随意执行docker volume prune、docker system prune、docker compose down -v。不随意挂载 Docker socket 到业务容器。13. 参考资料Docker Docs