【DockerK8s 实战·第一篇】Docker 基础容器化的第一步镜像、容器、Dockerfile 全解析更新时间2026-05-19 |阅读时长约 22 分钟系列Docker K8s 实战共 8 篇环境Docker 26.xUbuntu 22.04 / macOS标签Docker容器化Dockerfile镜像容器DevOps云原生系列规划篇次主题状态第一篇本篇Docker 基础镜像、容器、Dockerfile—第二篇Dockerfile 进阶多阶段构建与生产级镜像即将发布第三篇Docker Compose多容器编排实战即将发布第四篇K8s 核心概念Pod、Service、Deployment即将发布第五篇K8s 配置与存储ConfigMap、Secret、PV即将发布第六篇K8s 网络与服务发现Ingress、DNS即将发布第七篇K8s 生产实践HPA、滚动更新、健康检查即将发布第八篇CI/CD 完整流水线从代码到 K8s即将发布目录一、为什么需要容器化二、Docker 核心概念三、安装 Docker四、镜像操作pull、build、tag、push五、容器操作run、exec、logs、inspect六、Dockerfile构建自己的镜像七、数据卷持久化容器数据八、网络容器间通信九、完整实战容器化一个 Node.js 应用一、为什么需要容器化1.1 经典的在我机器上能跑问题传统部署的痛点 开发环境macOSNode 18Python 3.11MySQL 8.0 测试环境Ubuntu 20.04Node 16Python 3.9MySQL 5.7 生产环境CentOS 7Node 14Python 3.6MySQL 5.6 结果 开发✅ 本地跑得好好的 测试❌ Python 语法错误3.11 vs 3.9 生产❌ MySQL 语法不兼容8.0 vs 5.6 运维 在我机器上能跑啊... Docker 的解决方案 把应用和它所需的所有依赖运行时、库、配置 打包成一个标准化的集装箱容器 任何安装了 Docker 的机器上都能运行 开发、测试、生产环境完全一致1.2 容器 vs 虚拟机虚拟机VM ┌─────────────────────────────────┐ │ App A │ App B │ App C │ ├─────────┼─────────┼─────────────┤ │ Guest OS│ Guest OS│ Guest OS │ ← 每个 VM 有完整操作系统GB 级 ├─────────────────────────────────┤ │ Hypervisor │ ├─────────────────────────────────┤ │ Host OS │ ├─────────────────────────────────┤ │ Hardware │ └─────────────────────────────────┘ Docker 容器 ┌─────────────────────────────────┐ │ App A │ App B │ App C │ ├─────────┼─────────┼─────────────┤ │Container│Container│ Container │ ← 只有应用和依赖MB 级 ├─────────────────────────────────┤ │ Docker Engine │ ← 共享 Host OS 内核 ├─────────────────────────────────┤ │ Host OS │ ├─────────────────────────────────┤ │ Hardware │ └─────────────────────────────────┘ 对比 虚拟机 容器 启动时间 几分钟 秒级甚至毫秒 镜像大小 GB 级 MB 级 性能损耗 较大 极小接近原生 隔离级别 强独立内核 中等共享内核 适合场景 强隔离/不同OS 快速部署/微服务二、Docker 核心概念三个核心概念的关系 Dockerfile →build→ 镜像Image →run→ 容器Container 构建脚本 只读模板/快照 运行中的实例 类比 Dockerfile 菜谱 镜像 按菜谱做出的成品冷冻保存 容器 把成品加热后摆上桌运行中的份饭 可以从同一个镜像运行多个容器多份饭 停止容器不删除镜像冷冻起来下次还能用 镜像的分层结构 镜像 多个只读层Layer叠加 每条 Dockerfile 指令 一个新层 层的复用多个镜像共享相同的基础层节省空间 ubuntu:22.04 ← 基础层100MB 安装 Node.js ← 新增层50MB 复制应用代码 ← 新增层5MB 容器运行时层 ← 可写层运行中产生的变化三、安装 Docker3.1 LinuxUbuntu# 1. 卸载旧版本如果有sudoaptremovedockerdocker-engine docker.io containerd runc# 2. 安装依赖sudoaptupdatesudoaptinstall-yca-certificatescurlgnupg lsb-release# 3. 添加 Docker 官方 GPG 密钥sudomkdir-p/etc/apt/keyringscurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg|\sudogpg--dearmor-o/etc/apt/keyrings/docker.gpg# 4. 添加 Docker 软件源echo\deb [arch$(dpkg --print-architecture)signed-by/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \$(lsb_release-cs)stable|\sudotee/etc/apt/sources.list.d/docker.list/dev/null# 5. 安装 Docker Enginesudoaptupdatesudoaptinstall-ydocker-ce docker-ce-cli containerd.io docker-compose-plugin# 6. 将当前用户加入 docker 组避免每次都要 sudosudousermod-aGdocker$USERnewgrpdocker# 立即生效或重新登录# 7. 验证安装docker--version# Docker version 26.x.xdockercompose version# Docker Compose version v2.x.x3.2 macOS / Windows# macOS / Windows安装 Docker Desktop# 下载地址https://www.docker.com/products/docker-desktop/# 安装后即包含 Docker Engine Docker Compose# 验证dockerrun hello-world# 输出 Hello from Docker! 说明安装成功3.3 配置镜像加速国内用户// /etc/docker/daemon.jsonLinux// 或 Docker Desktop → Settings → Docker Engine{registry-mirrors:[https://mirror.ccs.tencentyun.com,https://registry.docker-cn.com,https://docker.mirrors.ustc.edu.cn],log-driver:json-file,log-opts:{max-size:100m,max-file:3}}# 重启 Docker 使配置生效sudosystemctl restartdocker四、镜像操作pull、build、tag、push4.1 拉取与查看镜像# 从 Docker Hub 拉取镜像dockerpull ubuntu:22.04# 指定 tag版本dockerpull nginx# 不指定 tag latestdockerpull node:20-alpine# alpine 极小版本推荐生产使用# 查看本地镜像dockerimages# REPOSITORY TAG IMAGE ID CREATED SIZE# ubuntu 22.04 8a3cdc4d1ad3 2 weeks ago 77.9MB# nginx latest 8f07b6fa5ffe 3 weeks ago 192MB# node 20-alpine 54c43a45c48d 4 weeks ago 169MB# 查看镜像详细信息dockerinspect ubuntu:22.04# 查看镜像层分层结构dockerhistorynode:20-alpine# 删除镜像dockerrmi nginx# 删除 nginx:latestdockerrmi 8a3cdc4d1ad3# 用 IMAGE ID 删除dockerimage prune# 删除所有未使用的镜像dockerimage prune-a# 删除所有没有容器使用的镜像4.2 搜索镜像# 在 Docker Hub 搜索dockersearch nginx# NAME DESCRIPTION STARS OFFICIAL# nginx Official build of Nginx. 18000 [OK]# nginx/nginx-ingress NGINX Ingress ... 89# 建议直接去 https://hub.docker.com 搜索# 可以看到详细说明、所有 tags、使用文档4.3 推送镜像到 Registry# 登录 Docker Hubdockerlogin# 或登录私有 Registrydockerlogin registry.example.com# 打 tag格式registry/username/repository:tagdockertag my-app:1.0.0 myusername/my-app:1.0.0dockertag my-app:1.0.0 myusername/my-app:latest# 推送到 Docker Hubdockerpush myusername/my-app:1.0.0dockerpush myusername/my-app:latest# 推送到私有 Registrydockertag my-app:1.0.0 registry.example.com/my-app:1.0.0dockerpush registry.example.com/my-app:1.0.0# 保存镜像为本地文件离线传输dockersave-omy-app.tar my-app:1.0.0# 加载镜像文件dockerload-imy-app.tar五、容器操作run、exec、logs、inspect5.1 运行容器# 基本运行dockerrun nginx# 常用参数dockerrun\--namemy-nginx\# 容器名称-d\# 后台运行detached-p8080:80\# 端口映射宿主机8080 → 容器80-p443:443\# 可以映射多个端口-v/host/path:/container/path\# 挂载数据卷-eMY_ENVvalue\# 设置环境变量--restartunless-stopped\# 重启策略nginx:latest# 交互式运行进入容器 shelldockerrun-itubuntu:22.04 /bin/bash# -i保持 stdin 打开# -t分配伪终端# 运行后自动删除一次性任务dockerrun--rmubuntu:22.04echoHello# 限制资源dockerrun\--memory512m\# 内存限制--cpus1.5\# CPU 限制1.5 个核心my-app:1.0.05.2 管理运行中的容器# 查看容器dockerps# 运行中的容器dockerps-a# 所有容器含已停止的dockerps--formattable {{.Names}}\t{{.Status}}\t{{.Ports}}# 停止和启动dockerstop my-nginx# 优雅停止发送 SIGTERM等待15秒dockerkillmy-nginx# 强制停止发送 SIGKILLdockerstart my-nginx# 启动已停止的容器dockerrestart my-nginx# 重启# 删除容器dockerrmmy-nginx# 删除已停止的容器dockerrm-fmy-nginx# 强制删除含运行中dockercontainer prune# 删除所有已停止的容器# 批量清理谨慎使用dockersystem prune# 删除未使用的资源dockersystem prune-a--volumes# 更彻底的清理5.3 进入容器与日志# 进入运行中的容器dockerexec-itmy-nginx /bin/bashdockerexec-itmy-nginx /bin/sh# alpine 系统用 sh# 在容器中执行单条命令不进入交互式 shelldockerexecmy-nginx nginx-t# 检查 nginx 配置dockerexecmy-nginxcat/etc/nginx/nginx.conf# 查看日志dockerlogs my-nginx# 所有日志dockerlogs-fmy-nginx# 实时跟踪followdockerlogs--tail100my-nginx# 最后 100 行dockerlogs--since1h my-nginx# 最近1小时的日志dockerlogs--since2026-05-19T10:00:00my-nginx# 指定时间# 查看容器详细信息dockerinspect my-nginx# JSON 格式的完整信息dockerinspect my-nginx|grepIPAddress# 提取 IP 地址dockerinspect--format{{.NetworkSettings.IPAddress}}my-nginx# 查看容器资源使用dockerstats# 实时监控所有容器资源dockerstats my-nginx# 监控指定容器dockerstats --no-stream# 只输出一次不持续# 复制文件dockercpmy-nginx:/etc/nginx/nginx.conf ./nginx.conf# 容器→宿主机dockercp./nginx.conf my-nginx:/etc/nginx/nginx.conf# 宿主机→容器六、Dockerfile构建自己的镜像6.1 Dockerfile 指令详解# 基础镜像必须是第一条非注释指令 FROM node:20-alpine # 设置环境变量构建时和运行时都可用 ENV NODE_ENVproduction \ PORT3000 \ APP_HOME/app # 设置工作目录后续命令在此目录执行 WORKDIR $APP_HOME # 复制文件从构建上下文到镜像 # COPY src dest COPY package*.json ./ # 先复制 package.json利用缓存 COPY . . # 再复制其余文件 # 添加文件类似 COPY但可以解压 tar支持 URL # ADD https://example.com/file.tar.gz /tmp/ # 不推荐用 URL用 RUN curl 更透明 ADD app.tar.gz /app # 会自动解压 tar # 执行命令每条 RUN 创建新的层 # 多条命令用 连接减少层数 RUN npm ci --onlyproduction \ npm cache clean --force \ rm -rf /tmp/* # 声明容器监听的端口文档性质不实际映射 EXPOSE 3000 # 设置数据卷挂载点 VOLUME [/app/logs, /app/uploads] # ARG仅构建时可用的变量与 ENV 的区别 ARG BUILD_VERSIONunknown RUN echo Building version: $BUILD_VERSION # LABEL为镜像添加元数据 LABEL maintaineryournameexample.com \ version1.0.0 \ descriptionMy Node.js App # 健康检查 HEALTHCHECK --interval30s --timeout10s --retries3 \ CMD wget -q --spider http://localhost:3000/health || exit 1 # 指定运行时用户安全最佳实践不用 root USER node # ENTRYPOINT容器启动时执行的命令不易被覆盖 ENTRYPOINT [node] # CMDENTRYPOINT 的默认参数可被 docker run 后的命令覆盖 CMD [server.js] # 等价于运行node server.js # docker run my-app other.js → 运行 node other.jsCMD 被覆盖6.2 ENTRYPOINT vs CMD# 方式1只用 CMD可以完全覆盖 CMD [node, server.js] # docker run my-app → node server.js # docker run my-app python app.py → python app.py完全替换 # 方式2只用 ENTRYPOINT固定入口点 ENTRYPOINT [node, server.js] # docker run my-app → node server.js # docker run my-app --port 8080 → node server.js --port 8080追加参数 # 方式3ENTRYPOINT CMD最灵活推荐 ENTRYPOINT [node] CMD [server.js] # docker run my-app → node server.js # docker run my-app other.js → node other.js只替换 CMD 部分6.3 .dockerignore# .dockerignore类似 .gitignore排除不需要放入镜像的文件 # 依赖目录会重新安装不需要复制 node_modules/ __pycache__/ *.pyc .venv/ # 版本控制 .git/ .gitignore # 构建产物 dist/ build/ *.egg-info/ # 测试文件 tests/ *.test.js *.spec.ts coverage/ # 配置文件可能含敏感信息 .env .env.local .env.*.local # IDE 文件 .vscode/ .idea/ *.swp # 文档 README.md docs/七、数据卷持久化容器数据容器的文件系统是临时的 容器删除 → 容器内的数据全部丢失 需要持久化的数据类型 数据库文件MySQL、PostgreSQL、MongoDB 用户上传的文件 应用日志 配置文件 解决方案Docker Volumes数据卷7.1 三种挂载方式# ── 方式1Named Volume命名数据卷推荐────────────────# Docker 管理存储位置跨平台一致dockervolume create mydata# 创建命名数据卷dockerrun-vmydata:/var/lib/mysql mysql:8.0# 使用dockervolumels# 查看所有数据卷dockervolume inspect mydata# 查看详情包括实际路径dockervolumermmydata# 删除dockervolume prune# 删除未使用的数据卷# ── 方式2Bind Mount绑定挂载────────────────────────────# 直接挂载宿主机目录开发时常用代码热更新dockerrun-v/host/data:/container/data nginxdockerrun-v$(pwd)/config:/app/config my-app# 挂载当前目录# ── 方式3tmpfs Mount内存挂载────────────────────────────# 数据存在内存中容器停止即消失# 适合敏感数据密钥、临时文件dockerrun--tmpfs/app/cache my-app# ── 只读挂载 ─────────────────────────────────────────────────dockerrun-vmyconfig:/app/config:ro my-app# :ro read only7.2 MySQL 持久化示例# 创建命名数据卷dockervolume create mysql-data# 运行 MySQL数据持久化到数据卷dockerrun\--namemysql\-d\-p3306:3306\-eMYSQL_ROOT_PASSWORDsecret123\-eMYSQL_DATABASEmydb\-eMYSQL_USERappuser\-eMYSQL_PASSWORDapppass\-vmysql-data:/var/lib/mysql\# 数据库文件持久化--restartunless-stopped\mysql:8.0# 验证停止并删除容器数据依然存在dockerstop mysqldockerrmmysqldockerrun--namemysql-new-d-p3306:3306\-eMYSQL_ROOT_PASSWORDsecret123\-vmysql-data:/var/lib/mysql\# 挂载同一个数据卷mysql:8.0# 之前的数据库和数据完全保留八、网络容器间通信8.1 Docker 网络模式# 查看网络dockernetworkls# NETWORK ID NAME DRIVER SCOPE# abc123 bridge bridge local ← 默认# def456 host host local# ghi789 none null local# bridge默认容器通过虚拟网桥通信有独立 IP# host容器直接使用宿主机网络性能最好隔离性差# none没有网络最高隔离# overlay跨主机容器通信K8s/Swarm 使用8.2 自定义网络推荐# 创建自定义 bridge 网络dockernetwork create myapp-network# 运行容器并加入网络dockerrun-d--namemysql\--networkmyapp-network\-eMYSQL_ROOT_PASSWORDsecret\mysql:8.0dockerrun-d--nameapp\--networkmyapp-network\-p3000:3000\-eDB_HOSTmysql\# ← 直接用容器名作为主机名DNS 自动解析my-app:1.0.0# 自定义网络的优势# 1. 容器间可以用容器名通信不用记 IP# 2. 与其他网络隔离安全# 3. 自动 DNS 解析容器名 → IP# 连接/断开网络dockernetwork connect myapp-network my-containerdockernetwork disconnect myapp-network my-container# 查看网络详情连接了哪些容器dockernetwork inspect myapp-network九、完整实战容器化一个 Node.js 应用9.1 应用代码// server.js简单的 Express 应用importexpressfromexpressimport{createPool}frommysql2/promiseconstappexpress()constportprocess.env.PORT||3000app.use(express.json())// 数据库连接池constpoolcreatePool({host:process.env.DB_HOST||localhost,port:Number(process.env.DB_PORT)||3306,user:process.env.DB_USER||root,password:process.env.DB_PASSWORD||,database:process.env.DB_NAME||mydb,waitForConnections:true,connectionLimit:10,})// 健康检查接口app.get(/health,(req,res){res.json({status:ok,timestamp:newDate().toISOString()})})// 业务接口app.get(/users,async(req,res){try{const[rows]awaitpool.query(SELECT * FROM users LIMIT 10)res.json(rows)}catch(err){res.status(500).json({error:err.message})}})app.listen(port,(){console.log(Server running on port${port})})// package.json{name:my-node-app,version:1.0.0,type:module,scripts:{start:node server.js,dev:node --watch server.js},dependencies:{express:^4.18.2,mysql2:^3.6.0}}9.2 Dockerfile# Dockerfile FROM node:20-alpine # 安全创建非 root 用户 RUN addgroup -S appgroup adduser -S appuser -G appgroup # 工作目录 WORKDIR /app # 先复制 package.json利用层缓存 # 只有 package.json 变化时才重新 npm install COPY package*.json ./ # 安装生产依赖跳过 devDependencies RUN npm ci --onlyproduction npm cache clean --force # 复制应用代码 COPY --chownappuser:appgroup . . # 切换到非 root 用户 USER appuser # 声明端口 EXPOSE 3000 # 健康检查 HEALTHCHECK --interval30s --timeout5s --start-period10s --retries3 \ CMD wget -qO- http://localhost:3000/health || exit 1 # 启动命令 CMD [node, server.js]9.3 构建与运行# 构建镜像dockerbuild-tmy-node-app:1.0.0.dockerbuild-tmy-node-app:1.0.0 --build-argNODE_ENVproduction.# 查看构建结果dockerimages|grepmy-node-appdockerhistorymy-node-app:1.0.0# 查看各层大小# 创建网络和数据卷dockernetwork create myapp-netdockervolume create mysql-data# 运行 MySQLdockerrun-d\--namemysql\--networkmyapp-net\-vmysql-data:/var/lib/mysql\-eMYSQL_ROOT_PASSWORDsecret123\-eMYSQL_DATABASEmydb\-eMYSQL_USERappuser\-eMYSQL_PASSWORDapppass\--restartunless-stopped\mysql:8.0# 等待 MySQL 启动约10秒sleep10# 运行应用dockerrun-d\--namemy-app\--networkmyapp-net\-p3000:3000\-eDB_HOSTmysql\-eDB_USERappuser\-eDB_PASSWORDapppass\-eDB_NAMEmydb\--restartunless-stopped\my-node-app:1.0.0# 验证dockerps# 检查两个容器都在运行dockerlogs-fmy-app# 查看应用日志curlhttp://localhost:3000/health# 测试健康检查# {status:ok,timestamp:2026-05-19T...}# 进入容器调试dockerexec-itmy-appsh9.4 完整的 Makefile常用命令封装# Makefile IMAGE_NAME : my-node-app VERSION : 1.0.0 .PHONY: build run stop logs clean build: docker build -t $(IMAGE_NAME):$(VERSION) . docker tag $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest run: docker network create myapp-net 2/dev/null || true docker volume create mysql-data 2/dev/null || true docker run -d --name mysql --network myapp-net \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDsecret123 \ -e MYSQL_DATABASEmydb \ mysql:8.0 docker run -d --name my-app --network myapp-net \ -p 3000:3000 \ -e DB_HOSTmysql \ $(IMAGE_NAME):$(VERSION) stop: docker stop my-app mysql 2/dev/null || true docker rm my-app mysql 2/dev/null || true logs: docker logs -f my-app shell: docker exec -it my-app sh clean: stop docker rmi $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest 2/dev/null || true docker network rm myapp-net 2/dev/null || truemakebuild# 构建镜像makerun# 启动所有服务makelogs# 查看日志makestop# 停止服务makeclean# 清理所有资源总结概念说明关键命令镜像Image只读模板应用的快照pullbuildtagpushrmi容器Container镜像的运行实例runstopstartrmexec数据卷Volume持久化容器数据volume create/ls/rm-v挂载网络Network容器间通信network create--networkDockerfile构建镜像的脚本FROMRUNCOPYCMDEXPOSEDockerfile 最佳实践速记用轻量基础镜像alpine、slim合并RUN命令减少层数先复制package.json再复制代码利用层缓存用.dockerignore排除无用文件不用root运行应用创建普通用户添加HEALTHCHECK下一篇预告Dockerfile 进阶——多阶段构建把 1GB 镜像压缩到 50MB、构建缓存优化、跨平台构建ARM/AMD64、镜像安全扫描。你们公司现在是用 Docker 部署应用吗还是还在传统方式欢迎评论区分享如果这篇帮到你点赞 收藏系列持续更新本文为原创技术分享。环境Docker 26.xUbuntu 22.04。最后更新2026-05-19
【Docker+K8s 实战·第一篇】Docker 基础:容器化的第一步,镜像、容器、Dockerfile 全解析
发布时间:2026/5/21 8:53:45
【DockerK8s 实战·第一篇】Docker 基础容器化的第一步镜像、容器、Dockerfile 全解析更新时间2026-05-19 |阅读时长约 22 分钟系列Docker K8s 实战共 8 篇环境Docker 26.xUbuntu 22.04 / macOS标签Docker容器化Dockerfile镜像容器DevOps云原生系列规划篇次主题状态第一篇本篇Docker 基础镜像、容器、Dockerfile—第二篇Dockerfile 进阶多阶段构建与生产级镜像即将发布第三篇Docker Compose多容器编排实战即将发布第四篇K8s 核心概念Pod、Service、Deployment即将发布第五篇K8s 配置与存储ConfigMap、Secret、PV即将发布第六篇K8s 网络与服务发现Ingress、DNS即将发布第七篇K8s 生产实践HPA、滚动更新、健康检查即将发布第八篇CI/CD 完整流水线从代码到 K8s即将发布目录一、为什么需要容器化二、Docker 核心概念三、安装 Docker四、镜像操作pull、build、tag、push五、容器操作run、exec、logs、inspect六、Dockerfile构建自己的镜像七、数据卷持久化容器数据八、网络容器间通信九、完整实战容器化一个 Node.js 应用一、为什么需要容器化1.1 经典的在我机器上能跑问题传统部署的痛点 开发环境macOSNode 18Python 3.11MySQL 8.0 测试环境Ubuntu 20.04Node 16Python 3.9MySQL 5.7 生产环境CentOS 7Node 14Python 3.6MySQL 5.6 结果 开发✅ 本地跑得好好的 测试❌ Python 语法错误3.11 vs 3.9 生产❌ MySQL 语法不兼容8.0 vs 5.6 运维 在我机器上能跑啊... Docker 的解决方案 把应用和它所需的所有依赖运行时、库、配置 打包成一个标准化的集装箱容器 任何安装了 Docker 的机器上都能运行 开发、测试、生产环境完全一致1.2 容器 vs 虚拟机虚拟机VM ┌─────────────────────────────────┐ │ App A │ App B │ App C │ ├─────────┼─────────┼─────────────┤ │ Guest OS│ Guest OS│ Guest OS │ ← 每个 VM 有完整操作系统GB 级 ├─────────────────────────────────┤ │ Hypervisor │ ├─────────────────────────────────┤ │ Host OS │ ├─────────────────────────────────┤ │ Hardware │ └─────────────────────────────────┘ Docker 容器 ┌─────────────────────────────────┐ │ App A │ App B │ App C │ ├─────────┼─────────┼─────────────┤ │Container│Container│ Container │ ← 只有应用和依赖MB 级 ├─────────────────────────────────┤ │ Docker Engine │ ← 共享 Host OS 内核 ├─────────────────────────────────┤ │ Host OS │ ├─────────────────────────────────┤ │ Hardware │ └─────────────────────────────────┘ 对比 虚拟机 容器 启动时间 几分钟 秒级甚至毫秒 镜像大小 GB 级 MB 级 性能损耗 较大 极小接近原生 隔离级别 强独立内核 中等共享内核 适合场景 强隔离/不同OS 快速部署/微服务二、Docker 核心概念三个核心概念的关系 Dockerfile →build→ 镜像Image →run→ 容器Container 构建脚本 只读模板/快照 运行中的实例 类比 Dockerfile 菜谱 镜像 按菜谱做出的成品冷冻保存 容器 把成品加热后摆上桌运行中的份饭 可以从同一个镜像运行多个容器多份饭 停止容器不删除镜像冷冻起来下次还能用 镜像的分层结构 镜像 多个只读层Layer叠加 每条 Dockerfile 指令 一个新层 层的复用多个镜像共享相同的基础层节省空间 ubuntu:22.04 ← 基础层100MB 安装 Node.js ← 新增层50MB 复制应用代码 ← 新增层5MB 容器运行时层 ← 可写层运行中产生的变化三、安装 Docker3.1 LinuxUbuntu# 1. 卸载旧版本如果有sudoaptremovedockerdocker-engine docker.io containerd runc# 2. 安装依赖sudoaptupdatesudoaptinstall-yca-certificatescurlgnupg lsb-release# 3. 添加 Docker 官方 GPG 密钥sudomkdir-p/etc/apt/keyringscurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg|\sudogpg--dearmor-o/etc/apt/keyrings/docker.gpg# 4. 添加 Docker 软件源echo\deb [arch$(dpkg --print-architecture)signed-by/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \$(lsb_release-cs)stable|\sudotee/etc/apt/sources.list.d/docker.list/dev/null# 5. 安装 Docker Enginesudoaptupdatesudoaptinstall-ydocker-ce docker-ce-cli containerd.io docker-compose-plugin# 6. 将当前用户加入 docker 组避免每次都要 sudosudousermod-aGdocker$USERnewgrpdocker# 立即生效或重新登录# 7. 验证安装docker--version# Docker version 26.x.xdockercompose version# Docker Compose version v2.x.x3.2 macOS / Windows# macOS / Windows安装 Docker Desktop# 下载地址https://www.docker.com/products/docker-desktop/# 安装后即包含 Docker Engine Docker Compose# 验证dockerrun hello-world# 输出 Hello from Docker! 说明安装成功3.3 配置镜像加速国内用户// /etc/docker/daemon.jsonLinux// 或 Docker Desktop → Settings → Docker Engine{registry-mirrors:[https://mirror.ccs.tencentyun.com,https://registry.docker-cn.com,https://docker.mirrors.ustc.edu.cn],log-driver:json-file,log-opts:{max-size:100m,max-file:3}}# 重启 Docker 使配置生效sudosystemctl restartdocker四、镜像操作pull、build、tag、push4.1 拉取与查看镜像# 从 Docker Hub 拉取镜像dockerpull ubuntu:22.04# 指定 tag版本dockerpull nginx# 不指定 tag latestdockerpull node:20-alpine# alpine 极小版本推荐生产使用# 查看本地镜像dockerimages# REPOSITORY TAG IMAGE ID CREATED SIZE# ubuntu 22.04 8a3cdc4d1ad3 2 weeks ago 77.9MB# nginx latest 8f07b6fa5ffe 3 weeks ago 192MB# node 20-alpine 54c43a45c48d 4 weeks ago 169MB# 查看镜像详细信息dockerinspect ubuntu:22.04# 查看镜像层分层结构dockerhistorynode:20-alpine# 删除镜像dockerrmi nginx# 删除 nginx:latestdockerrmi 8a3cdc4d1ad3# 用 IMAGE ID 删除dockerimage prune# 删除所有未使用的镜像dockerimage prune-a# 删除所有没有容器使用的镜像4.2 搜索镜像# 在 Docker Hub 搜索dockersearch nginx# NAME DESCRIPTION STARS OFFICIAL# nginx Official build of Nginx. 18000 [OK]# nginx/nginx-ingress NGINX Ingress ... 89# 建议直接去 https://hub.docker.com 搜索# 可以看到详细说明、所有 tags、使用文档4.3 推送镜像到 Registry# 登录 Docker Hubdockerlogin# 或登录私有 Registrydockerlogin registry.example.com# 打 tag格式registry/username/repository:tagdockertag my-app:1.0.0 myusername/my-app:1.0.0dockertag my-app:1.0.0 myusername/my-app:latest# 推送到 Docker Hubdockerpush myusername/my-app:1.0.0dockerpush myusername/my-app:latest# 推送到私有 Registrydockertag my-app:1.0.0 registry.example.com/my-app:1.0.0dockerpush registry.example.com/my-app:1.0.0# 保存镜像为本地文件离线传输dockersave-omy-app.tar my-app:1.0.0# 加载镜像文件dockerload-imy-app.tar五、容器操作run、exec、logs、inspect5.1 运行容器# 基本运行dockerrun nginx# 常用参数dockerrun\--namemy-nginx\# 容器名称-d\# 后台运行detached-p8080:80\# 端口映射宿主机8080 → 容器80-p443:443\# 可以映射多个端口-v/host/path:/container/path\# 挂载数据卷-eMY_ENVvalue\# 设置环境变量--restartunless-stopped\# 重启策略nginx:latest# 交互式运行进入容器 shelldockerrun-itubuntu:22.04 /bin/bash# -i保持 stdin 打开# -t分配伪终端# 运行后自动删除一次性任务dockerrun--rmubuntu:22.04echoHello# 限制资源dockerrun\--memory512m\# 内存限制--cpus1.5\# CPU 限制1.5 个核心my-app:1.0.05.2 管理运行中的容器# 查看容器dockerps# 运行中的容器dockerps-a# 所有容器含已停止的dockerps--formattable {{.Names}}\t{{.Status}}\t{{.Ports}}# 停止和启动dockerstop my-nginx# 优雅停止发送 SIGTERM等待15秒dockerkillmy-nginx# 强制停止发送 SIGKILLdockerstart my-nginx# 启动已停止的容器dockerrestart my-nginx# 重启# 删除容器dockerrmmy-nginx# 删除已停止的容器dockerrm-fmy-nginx# 强制删除含运行中dockercontainer prune# 删除所有已停止的容器# 批量清理谨慎使用dockersystem prune# 删除未使用的资源dockersystem prune-a--volumes# 更彻底的清理5.3 进入容器与日志# 进入运行中的容器dockerexec-itmy-nginx /bin/bashdockerexec-itmy-nginx /bin/sh# alpine 系统用 sh# 在容器中执行单条命令不进入交互式 shelldockerexecmy-nginx nginx-t# 检查 nginx 配置dockerexecmy-nginxcat/etc/nginx/nginx.conf# 查看日志dockerlogs my-nginx# 所有日志dockerlogs-fmy-nginx# 实时跟踪followdockerlogs--tail100my-nginx# 最后 100 行dockerlogs--since1h my-nginx# 最近1小时的日志dockerlogs--since2026-05-19T10:00:00my-nginx# 指定时间# 查看容器详细信息dockerinspect my-nginx# JSON 格式的完整信息dockerinspect my-nginx|grepIPAddress# 提取 IP 地址dockerinspect--format{{.NetworkSettings.IPAddress}}my-nginx# 查看容器资源使用dockerstats# 实时监控所有容器资源dockerstats my-nginx# 监控指定容器dockerstats --no-stream# 只输出一次不持续# 复制文件dockercpmy-nginx:/etc/nginx/nginx.conf ./nginx.conf# 容器→宿主机dockercp./nginx.conf my-nginx:/etc/nginx/nginx.conf# 宿主机→容器六、Dockerfile构建自己的镜像6.1 Dockerfile 指令详解# 基础镜像必须是第一条非注释指令 FROM node:20-alpine # 设置环境变量构建时和运行时都可用 ENV NODE_ENVproduction \ PORT3000 \ APP_HOME/app # 设置工作目录后续命令在此目录执行 WORKDIR $APP_HOME # 复制文件从构建上下文到镜像 # COPY src dest COPY package*.json ./ # 先复制 package.json利用缓存 COPY . . # 再复制其余文件 # 添加文件类似 COPY但可以解压 tar支持 URL # ADD https://example.com/file.tar.gz /tmp/ # 不推荐用 URL用 RUN curl 更透明 ADD app.tar.gz /app # 会自动解压 tar # 执行命令每条 RUN 创建新的层 # 多条命令用 连接减少层数 RUN npm ci --onlyproduction \ npm cache clean --force \ rm -rf /tmp/* # 声明容器监听的端口文档性质不实际映射 EXPOSE 3000 # 设置数据卷挂载点 VOLUME [/app/logs, /app/uploads] # ARG仅构建时可用的变量与 ENV 的区别 ARG BUILD_VERSIONunknown RUN echo Building version: $BUILD_VERSION # LABEL为镜像添加元数据 LABEL maintaineryournameexample.com \ version1.0.0 \ descriptionMy Node.js App # 健康检查 HEALTHCHECK --interval30s --timeout10s --retries3 \ CMD wget -q --spider http://localhost:3000/health || exit 1 # 指定运行时用户安全最佳实践不用 root USER node # ENTRYPOINT容器启动时执行的命令不易被覆盖 ENTRYPOINT [node] # CMDENTRYPOINT 的默认参数可被 docker run 后的命令覆盖 CMD [server.js] # 等价于运行node server.js # docker run my-app other.js → 运行 node other.jsCMD 被覆盖6.2 ENTRYPOINT vs CMD# 方式1只用 CMD可以完全覆盖 CMD [node, server.js] # docker run my-app → node server.js # docker run my-app python app.py → python app.py完全替换 # 方式2只用 ENTRYPOINT固定入口点 ENTRYPOINT [node, server.js] # docker run my-app → node server.js # docker run my-app --port 8080 → node server.js --port 8080追加参数 # 方式3ENTRYPOINT CMD最灵活推荐 ENTRYPOINT [node] CMD [server.js] # docker run my-app → node server.js # docker run my-app other.js → node other.js只替换 CMD 部分6.3 .dockerignore# .dockerignore类似 .gitignore排除不需要放入镜像的文件 # 依赖目录会重新安装不需要复制 node_modules/ __pycache__/ *.pyc .venv/ # 版本控制 .git/ .gitignore # 构建产物 dist/ build/ *.egg-info/ # 测试文件 tests/ *.test.js *.spec.ts coverage/ # 配置文件可能含敏感信息 .env .env.local .env.*.local # IDE 文件 .vscode/ .idea/ *.swp # 文档 README.md docs/七、数据卷持久化容器数据容器的文件系统是临时的 容器删除 → 容器内的数据全部丢失 需要持久化的数据类型 数据库文件MySQL、PostgreSQL、MongoDB 用户上传的文件 应用日志 配置文件 解决方案Docker Volumes数据卷7.1 三种挂载方式# ── 方式1Named Volume命名数据卷推荐────────────────# Docker 管理存储位置跨平台一致dockervolume create mydata# 创建命名数据卷dockerrun-vmydata:/var/lib/mysql mysql:8.0# 使用dockervolumels# 查看所有数据卷dockervolume inspect mydata# 查看详情包括实际路径dockervolumermmydata# 删除dockervolume prune# 删除未使用的数据卷# ── 方式2Bind Mount绑定挂载────────────────────────────# 直接挂载宿主机目录开发时常用代码热更新dockerrun-v/host/data:/container/data nginxdockerrun-v$(pwd)/config:/app/config my-app# 挂载当前目录# ── 方式3tmpfs Mount内存挂载────────────────────────────# 数据存在内存中容器停止即消失# 适合敏感数据密钥、临时文件dockerrun--tmpfs/app/cache my-app# ── 只读挂载 ─────────────────────────────────────────────────dockerrun-vmyconfig:/app/config:ro my-app# :ro read only7.2 MySQL 持久化示例# 创建命名数据卷dockervolume create mysql-data# 运行 MySQL数据持久化到数据卷dockerrun\--namemysql\-d\-p3306:3306\-eMYSQL_ROOT_PASSWORDsecret123\-eMYSQL_DATABASEmydb\-eMYSQL_USERappuser\-eMYSQL_PASSWORDapppass\-vmysql-data:/var/lib/mysql\# 数据库文件持久化--restartunless-stopped\mysql:8.0# 验证停止并删除容器数据依然存在dockerstop mysqldockerrmmysqldockerrun--namemysql-new-d-p3306:3306\-eMYSQL_ROOT_PASSWORDsecret123\-vmysql-data:/var/lib/mysql\# 挂载同一个数据卷mysql:8.0# 之前的数据库和数据完全保留八、网络容器间通信8.1 Docker 网络模式# 查看网络dockernetworkls# NETWORK ID NAME DRIVER SCOPE# abc123 bridge bridge local ← 默认# def456 host host local# ghi789 none null local# bridge默认容器通过虚拟网桥通信有独立 IP# host容器直接使用宿主机网络性能最好隔离性差# none没有网络最高隔离# overlay跨主机容器通信K8s/Swarm 使用8.2 自定义网络推荐# 创建自定义 bridge 网络dockernetwork create myapp-network# 运行容器并加入网络dockerrun-d--namemysql\--networkmyapp-network\-eMYSQL_ROOT_PASSWORDsecret\mysql:8.0dockerrun-d--nameapp\--networkmyapp-network\-p3000:3000\-eDB_HOSTmysql\# ← 直接用容器名作为主机名DNS 自动解析my-app:1.0.0# 自定义网络的优势# 1. 容器间可以用容器名通信不用记 IP# 2. 与其他网络隔离安全# 3. 自动 DNS 解析容器名 → IP# 连接/断开网络dockernetwork connect myapp-network my-containerdockernetwork disconnect myapp-network my-container# 查看网络详情连接了哪些容器dockernetwork inspect myapp-network九、完整实战容器化一个 Node.js 应用9.1 应用代码// server.js简单的 Express 应用importexpressfromexpressimport{createPool}frommysql2/promiseconstappexpress()constportprocess.env.PORT||3000app.use(express.json())// 数据库连接池constpoolcreatePool({host:process.env.DB_HOST||localhost,port:Number(process.env.DB_PORT)||3306,user:process.env.DB_USER||root,password:process.env.DB_PASSWORD||,database:process.env.DB_NAME||mydb,waitForConnections:true,connectionLimit:10,})// 健康检查接口app.get(/health,(req,res){res.json({status:ok,timestamp:newDate().toISOString()})})// 业务接口app.get(/users,async(req,res){try{const[rows]awaitpool.query(SELECT * FROM users LIMIT 10)res.json(rows)}catch(err){res.status(500).json({error:err.message})}})app.listen(port,(){console.log(Server running on port${port})})// package.json{name:my-node-app,version:1.0.0,type:module,scripts:{start:node server.js,dev:node --watch server.js},dependencies:{express:^4.18.2,mysql2:^3.6.0}}9.2 Dockerfile# Dockerfile FROM node:20-alpine # 安全创建非 root 用户 RUN addgroup -S appgroup adduser -S appuser -G appgroup # 工作目录 WORKDIR /app # 先复制 package.json利用层缓存 # 只有 package.json 变化时才重新 npm install COPY package*.json ./ # 安装生产依赖跳过 devDependencies RUN npm ci --onlyproduction npm cache clean --force # 复制应用代码 COPY --chownappuser:appgroup . . # 切换到非 root 用户 USER appuser # 声明端口 EXPOSE 3000 # 健康检查 HEALTHCHECK --interval30s --timeout5s --start-period10s --retries3 \ CMD wget -qO- http://localhost:3000/health || exit 1 # 启动命令 CMD [node, server.js]9.3 构建与运行# 构建镜像dockerbuild-tmy-node-app:1.0.0.dockerbuild-tmy-node-app:1.0.0 --build-argNODE_ENVproduction.# 查看构建结果dockerimages|grepmy-node-appdockerhistorymy-node-app:1.0.0# 查看各层大小# 创建网络和数据卷dockernetwork create myapp-netdockervolume create mysql-data# 运行 MySQLdockerrun-d\--namemysql\--networkmyapp-net\-vmysql-data:/var/lib/mysql\-eMYSQL_ROOT_PASSWORDsecret123\-eMYSQL_DATABASEmydb\-eMYSQL_USERappuser\-eMYSQL_PASSWORDapppass\--restartunless-stopped\mysql:8.0# 等待 MySQL 启动约10秒sleep10# 运行应用dockerrun-d\--namemy-app\--networkmyapp-net\-p3000:3000\-eDB_HOSTmysql\-eDB_USERappuser\-eDB_PASSWORDapppass\-eDB_NAMEmydb\--restartunless-stopped\my-node-app:1.0.0# 验证dockerps# 检查两个容器都在运行dockerlogs-fmy-app# 查看应用日志curlhttp://localhost:3000/health# 测试健康检查# {status:ok,timestamp:2026-05-19T...}# 进入容器调试dockerexec-itmy-appsh9.4 完整的 Makefile常用命令封装# Makefile IMAGE_NAME : my-node-app VERSION : 1.0.0 .PHONY: build run stop logs clean build: docker build -t $(IMAGE_NAME):$(VERSION) . docker tag $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest run: docker network create myapp-net 2/dev/null || true docker volume create mysql-data 2/dev/null || true docker run -d --name mysql --network myapp-net \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDsecret123 \ -e MYSQL_DATABASEmydb \ mysql:8.0 docker run -d --name my-app --network myapp-net \ -p 3000:3000 \ -e DB_HOSTmysql \ $(IMAGE_NAME):$(VERSION) stop: docker stop my-app mysql 2/dev/null || true docker rm my-app mysql 2/dev/null || true logs: docker logs -f my-app shell: docker exec -it my-app sh clean: stop docker rmi $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest 2/dev/null || true docker network rm myapp-net 2/dev/null || truemakebuild# 构建镜像makerun# 启动所有服务makelogs# 查看日志makestop# 停止服务makeclean# 清理所有资源总结概念说明关键命令镜像Image只读模板应用的快照pullbuildtagpushrmi容器Container镜像的运行实例runstopstartrmexec数据卷Volume持久化容器数据volume create/ls/rm-v挂载网络Network容器间通信network create--networkDockerfile构建镜像的脚本FROMRUNCOPYCMDEXPOSEDockerfile 最佳实践速记用轻量基础镜像alpine、slim合并RUN命令减少层数先复制package.json再复制代码利用层缓存用.dockerignore排除无用文件不用root运行应用创建普通用户添加HEALTHCHECK下一篇预告Dockerfile 进阶——多阶段构建把 1GB 镜像压缩到 50MB、构建缓存优化、跨平台构建ARM/AMD64、镜像安全扫描。你们公司现在是用 Docker 部署应用吗还是还在传统方式欢迎评论区分享如果这篇帮到你点赞 收藏系列持续更新本文为原创技术分享。环境Docker 26.xUbuntu 22.04。最后更新2026-05-19