90分钟Docker实战:从容器化原理到项目部署全流程 想花90分钟把Docker从零学到能部署项目这篇教程应该能帮到你。先说一个真实场景你在一台服务器上手动装好了JDK、MySQL、Redis项目终于跑起来了。过了两周新同事加入你花了一下午帮他重复同样的环境配置。再过一个月服务器迁移你发现又要重新装一遍而且上次装的MySQL版本和这次还不一样。这种重复劳动正是容器化要解决的核心问题。Docker解决的不是“某个软件怎么安装”而是“整个运行环境怎么打包迁移”。它把项目代码、操作系统依赖、运行时、配置文件全部装进一个标准化的“集装箱”里在任何装有Docker的机器上都能启动。这也是它成为运维和开发同学必备技能的原因。这篇实战教程会按两条线展开一条是Linux基础命令与Docker命令的对应另一条是从安装Docker开始到拉取镜像、运行容器、构建项目镜像、部署到服务器的完整流程。全程90分钟适合刚接触运维、想要容器化改造业务系统、或者准备Linux面试的读者。1. 别急着装Docker先理解容器化到底解决了什么问题很多人第一次接触Docker会觉得它只是“一个更轻量的虚拟机”。这个理解方向对了一半但容易忽略容器化真正带来的工程价值。回到开头那个场景。传统部署方式下环境依赖是最大的坑。你的JDK可能是1.8服务器上却是17你的MySQL用了utf8mb4默认配置却是latin1你的Redis密码写在application.properties里换一台机器又要改。这些差异不会在开发环境暴露一到部署阶段就变成一连串“在我电脑上明明是好的”问题。Docker把答案标准化了。你不再面对“一台裸Linux服务器”而是面对“一堆容器”。每个容器都是一个隔离的运行环境里面有指定的OS基础镜像、指定的软件版本、指定的环境变量。开发环境、测试环境、生产环境都用同一个镜像启动行为完全一致。还有一个容易被忽视的点资源密度。如果一台服务器要跑Java后端、Python爬虫、前端Nginx、MySQL、Redis传统做法是装五个软件、管理五个进程、开五个端口每一层都可能冲突。用容器之后这些服务各自运行在独立容器里互不干扰删除某个服务也干净。从运维视角看Docker的三大收益分别是环境标准化、交付物标准化、扩缩容便捷化。从开发视角看收益是“本地能跑 线上能跑”排查问题的范围大幅缩小。有了这个认知基础后面学习的命令和配置就不再是死记硬背而是一个统一逻辑下的具体操作。2. Docker的核心概念镜像、容器、仓库Docker有三个核心概念把这三个概念理解透80%的命令都可以自己推出来。镜像Image是一个只读模板里面包含了运行某个应用所需的全部文件。你可以把它理解成一个操作系统的“Ghost镜像”或者一个Java中的Class。类本身不能执行但可以实例化出多个对象。镜像也一样本身不能被修改但可以从它启动出多个独立的运行实例。容器Container就是镜像的运行实例。每个容器有自己的文件系统、网络栈、进程空间可以启动、停止、删除。多个容器之间互不影响就像同一个类new出来的多个对象拥有各自的成员变量一样。容器在运行过程中产生的文件变更默认只写在容器自己的可写层镜像本身不会被改变。仓库Repository是集中存放镜像的地方。最常见的公开仓库是Docker Hub国内使用通常还需要配置镜像加速器否则拉取镜像会很慢。仓库的概念类似GitHub镜像就是仓库里的“代码包”通过docker pull下载通过docker push上传。三个概念串起来就是一条流水线开发者在Dockerfile中描述“这个镜像需要什么底料、安装什么依赖、执行什么命令”build之后得到镜像镜像推送到仓库服务器从仓库拉取镜像docker run启动容器容器里跑的就是业务应用。对比记忆可以这样理解镜像 图纸容器 根据图纸造出来的房子仓库 存放图纸的档案馆。图纸可以反复造房子每栋房子独立装修互不干扰档案馆负责图纸的分发和版本管理。3. 环境准备Linux基础命令与Docker安装3.1 前置要求学习Docker最好准备一台Linux服务器或虚拟机。云服务器比如阿里云ECS、虚拟机VirtualBox或VMware都可以操作系统推荐CentOS 7.9或Ubuntu 22.04 LTS。Windows用户也可以装Docker Desktop但目前国内使用Docker Desktop涉及虚拟化检测等问题入门阶段更推荐直接用Linux环境。动手之前先把这几个Linux基础命令搞明白。这是后面所有操作的前提# 查看系统版本 cat /etc/os-release # 切换root用户或使用sudo提权 sudo -i # 查看当前目录 pwd # 列出目录文件 ls -l # 新建和删除文件/目录 mkdir -p /data/app touch /data/app/hello.txt rm -rf /data/app # 查看磁盘和内存 df -h free -h # 查看某端口是否被占用 netstat -tlnp | grep 3306这里特别说一下rm -rf的危险性。在Linux下执行删除操作时没有“回收站”rm -rf /data/app会直接删除整个目录且无法恢复。生产环境务必先ls确认路径再执行删除。3.2 安装Docker以CentOS和Ubuntu为例分别给出安装命令。CentOS 7.x安装Docker# 如果系统里装了旧版docker先卸载 yum remove docker docker-client docker-common docker-selinux docker-engine # 安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 配置yum源这里用阿里云镜像解决国内下载慢问题 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 安装docker yum install -y docker-ce docker-ce-cli containerd.io # 启动并设置开机自启 systemctl start docker systemctl enable dockerUbuntu 22.04安装Docker# 更新apt源 apt update # 安装依赖 apt install -y apt-transport-https ca-certificates curl software-properties-common # 添加官方GPG密钥这里使用清华镜像源 curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | apt-key add - # 添加软件源 add-apt-repository deb [archamd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable # 安装docker apt install -y docker-ce docker-ce-cli containerd.io # 启动并设置开机自启 systemctl start docker systemctl enable docker安装完成之后验证docker version docker info出现Client和Server两段信息说明安装成功。注意只有Client没有Server通常意味着dockerd进程没有启动执行systemctl start docker即可。3.3 配置镜像加速器国内拉取Docker官方镜像经常超时这是新手遇到最多的坑。解决方式是配置镜像加速器。修改/etc/docker/daemon.jsonmkdir -p /etc/docker cat /etc/docker/daemon.json EOF { registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] } EOF systemctl daemon-reload systemctl restart docker加速器的可用性会随时间变化如果某个地址失效换成剩下的地址即可。配置完成后执行docker info在Registry Mirrors一栏可以看到配置结果。4. Docker常用命令实战镜像管理和容器生命周期4.1 镜像管理命令镜像相关命令较少但每一个都要熟练掌握。# 搜索镜像 docker search mysql # 拉取镜像默认从Docker Hub拉取latest标签 docker pull mysql:8.0 # 查看本地镜像列表 docker images # 查看镜像详细信息 docker inspect mysql:8.0 # 删除镜像先删除依赖它的容器 docker rmi mysql:8.0 # 清理所有无用的悬空镜像 docker image prune拉取镜像时需要注意标签tag。mysql:8.0表示8.0版本的镜像mysql:latest是最近发布的稳定版。生产环境中强烈建议使用明确版本号这样镜像内容可控也方便回滚。4.2 容器生命周期命令以Nginx为例跑通容器从创建到删除的完整生命周期。# 启动一个nginx容器宿主机80端口映射到容器80端口 docker run -d --name web01 -p 80:80 nginx # 查看容器列表 docker ps docker ps -a # 查看容器日志 docker logs web01 docker logs -f web01 # 进入容器内部 docker exec -it web01 bash # 停止容器 docker stop web01 # 启动已停止的容器 docker start web01 # 重启容器 docker restart web01 # 删除容器运行中的容器需要先stop或用-f强制删除 docker rm web01 docker rm -f web01这里拆解几个关键参数的含义-d后台运行不加这个参数会一直占据终端。--name给容器起名字便于管理。不加会自动生成随机名字。-p 80:80端口映射宿主机的80端口转发到容器的80端口。没有这个参数容器外部无法访问。-it分配交互式终端用于进入容器执行命令。最常见的启动命令是docker run -d -p 端口:端口 --name 名字 镜像名这个组合要练到顺手能写出来。4.3 Linux命令和Docker命令的对照关系对于Linux基础薄弱的读者这个对照表可以帮助你理解Docker命令的本质。Docker并没有发明一套全新的操作范式它只是把Linux的进程、文件、网络操作封装成更统一的命令。Linux操作Docker操作作用mkdirdocker pull准备环境依赖vim nginx.confdocker run -v写入配置systemctl start nginxdocker start web01启动服务systemctl stop nginxdocker stop web01停止服务less /var/log/nginx/error.logdocker logs web01查看日志topdocker stats查看资源占用rm -rf /usr/local/nginxdocker rm web01删除服务理解这一层之后学习曲线会平缓很多。你不是在学一套全新体系而是在学“用Docker的方式管理Linux服务”。5. 用Docker部署第一个完整项目Spring Boot MySQL Nginx前面的命令都是单容器操作这一节做一次真正的项目部署。假设你有一个Spring Boot项目需要部署到服务器同时依赖MySQL数据库前端静态文件交给Nginx处理。5.1 准备一个Spring Boot项目为了快速演示我们用一个极简的Spring Boot项目。核心是一个Controller返回一行数据库查询结果。首先写一个简单的Java文件。// 文件路径src/main/java/com/example/demo/DemoController.java package com.example.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; RestController public class DemoController { private final DataSource dataSource; public DemoController(DataSource dataSource) { this.dataSource dataSource; } GetMapping(/hello) public String hello() throws Exception { try (Connection conn dataSource.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT Hello Docker AS msg)) { rs.next(); return rs.getString(msg); } } }然后写项目配置文件。# 文件路径src/main/resources/application.properties spring.datasource.urljdbc:mysql://mysql-container:3306/demo_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernamedemo_user spring.datasource.passworddemo_pass spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver这里的mysql-container是MySQL容器的名字Docker网络内可以通过容器名互相访问不需要写IP地址。这是容器化部署和传统部署的一个重要区别。5.2 编写DockerfileDockerfile是构建镜像的配方文件。对于Java项目常见做法是使用多阶段构建先编译再打包运行镜像。# 文件路径Dockerfile # 第一阶段构建 FROM maven:3.8-openjdk-8 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests # 第二阶段运行 FROM openjdk:8-jdk-alpine WORKDIR /app COPY --frombuilder /build/target/demo.jar ./app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]Dockerfile的关键指令解释FROM基础镜像。第一阶段用Maven镜像完成编译第二阶段用轻量JDK镜像运行。WORKDIR设置容器内的工作目录。COPY把文件复制进镜像。RUN在镜像构建过程中执行命令。EXPOSE声明容器内监听的端口只是文档性质的声明真正暴露端口还是靠-p参数。ENTRYPOINT容器启动时执行的命令。用Maven镜像来打包意味着本地不需要安装Maven和JDK构建行为完全一致。5.3 启动MySQL容器在启动Spring Boot容器之前先准备好数据库依赖。# 创建Docker网络让容器可以通过容器名互相访问 docker network create demo-net # 启动MySQL容器 docker run -d \ --name mysql-container \ --network demo-net \ -e MYSQL_ROOT_PASSWORDroot_pass \ -e MYSQL_DATABASEdemo_db \ -e MYSQL_USERdemo_user \ -e MYSQL_PASSWORDdemo_pass \ -p 3306:3306 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0 # 查看MySQL容器是否正常运行 docker ps docker logs mysql-container这里几个要点-e设置环境变量MySQL官方镜像通过这些变量初始化数据库、创建用户和密码。首次启动MySQL时需要一定时间初始化可以执行docker logs mysql-container查看进度看到ready for connections表示启动完成。-v是数据卷挂载把容器的/var/lib/mysql目录映射到宿主机的/data/mysql。这是生产环境必须养成的习惯否则容器一旦删除数据库数据全部消失。安全提醒上面的账号密码是演示用途真实项目中请使用高强度密码不要复用root账号作为业务账号。5.4 构建并启动Spring Boot容器# 在项目根目录执行构建 docker build -t demo-app:v1 . # 启动容器加入demo-net网络 docker run -d \ --name demo-app \ --network demo-net \ -p 8080:8080 \ demo-app:v1启动后验证# 查看启动日志看到Tomcat started on port(s): 8080表示成功 docker logs demo-app # 测试接口 curl http://localhost:8080/hello如果一切正常curl会返回Hello Docker。5.5 用Nginx做反向代理实际项目中前端页面通过Nginx访问后端接口通过Nginx反向代理。用Nginx容器完成这一步。# 创建Nginx配置目录 mkdir -p /data/nginx/conf.d mkdir -p /data/nginx/html # 写入Nginx配置 cat /data/nginx/conf.d/default.conf EOF server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; } location /api/ { proxy_pass http://demo-app:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } EOF # 启动Nginx容器 docker run -d \ --name nginx-web \ --network demo-net \ -p 80:80 \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /data/nginx/html:/usr/share/nginx/html:ro \ nginx:alpine启动后访问http://服务器IP/可以看到前端页面访问http://服务器IP/api/helloNginx会把请求转发给demo-app:8080容器。这里proxy_pass http://demo-app:8080/中的demo-app就是Spring Boot容器的名字在同一个Docker网络中自动解析到对应容器IP这就是容器化内部服务调用的标准写法。整个部署完成后项目就由三个容器组成nginx-web负责流量入口demo-app负责业务逻辑mysql-container负责数据存储。三个容器彼此独立又互相通信全部通过docker run命令启动环境完全一致。6. 用docker-compose管理多容器服务上面的操作需要手动执行三条docker run命令而且每次都要记得加网络参数和挂载参数。如果项目依赖的服务更多比如加上Redis、RabbitMQ命令会越来越长容易出错。docker-compose就是用来解决这个问题的。它用一份YAML文件描述整个服务栈一条命令启动或停止全部服务。在项目根目录创建docker-compose.yml# 文件路径docker-compose.yml version: 3.8 services: mysql-container: image: mysql:8.0 container_name: mysql-container restart: always environment: MYSQL_ROOT_PASSWORD: root_pass MYSQL_DATABASE: demo_db MYSQL_USER: demo_user MYSQL_PASSWORD: demo_pass volumes: - /data/mysql:/var/lib/mysql ports: - 3306:3306 networks: - demo-net demo-app: build: . image: demo-app:v1 container_name: demo-app restart: always depends_on: - mysql-container ports: - 8080:8080 networks: - demo-net nginx-web: image: nginx:alpine container_name: nginx-web restart: always depends_on: - demo-app ports: - 80:80 volumes: - /data/nginx/conf.d:/etc/nginx/conf.d:ro - /data/nginx/html:/usr/share/nginx/html:ro networks: - demo-net networks: demo-net: driver: bridge启动整个项目# 构建并启动所有服务 docker-compose up -d # 查看服务状态 docker-compose ps # 查看日志 docker-compose logs -f # 停止所有服务 docker-compose down需要注意docker-compose down默认不会删除数据卷数据库数据还在。如果连数据一起清理使用docker-compose down -v这个命令在生产环境要慎用它会删除挂载卷中的数据。相比之前的流程docker-compose做的事情很简单自动创建网络自动设置依赖关系depends_on用一条命令管理所有容器。7. 常见问题与排查思路7.1 问题排查表格问题现象可能原因排查方式解决方案docker pull卡住或超时未配置镜像加速器直连Docker Hub过慢docker info查看Registry Mirrors配置国内镜像加速后重启Docker容器启动后立即退出应用启动失败或启动命令错误docker logs 容器名查看日志根据日志修正Dockerfile命令或配置端口无法访问防火墙未放行或云安全组未配置netstat -tlnp查看监听检查云控制台安全组放行对应端口进入容器报bash: command not found基础镜像里没有bash改用sh进入docker exec -it 容器名 sh容器名冲突同名容器已存在docker ps -a查看docker rm 旧容器后重新创建MySQL容器启动失败数据目录权限或端口被占用docker logs mysql-container检查netstat -tlnp端口占用确认数据目录可写容器间无法通过容器名访问不在同一个Docker网络docker inspect 容器名查看网络重新创建容器加入同一网络7.2 日志查看技巧容器日志是排查问题的第一入口。# 持续跟踪日志输出 docker logs -f --tail 100 容器名 # 查看某个时间点之后的日志 docker logs --since 2025-01-01T10:00:00 容器名 # 查看最近10分钟日志 docker logs --since 10m 容器名日志输出到stdout和stderr的都会被Docker捕获这就是为什么容器里的应用要尽量把日志打到控制台而不是文件。如果应用把日志写进了文件需要用docker exec进入容器查看。7.3 瘦身和资源占用检查# 查看每个容器的CPU和内存占用 docker stats # 查看Docker磁盘占用 docker system df # 一键清理停止的容器、无用网络、悬空镜像、构建缓存 docker system prune -afdocker system prune -af会删除所有未被容器使用的镜像生产环境执行前一定确认是否有需要保留的镜像。7.4 数据卷备份与恢复容器删除时数据会丢失所以数据卷的备份是个重要话题。以MySQL为例。# 备份 docker exec mysql-container sh -c exec mysqldump -udemo_user -pdemo_pass demo_db /data/backup/demo_db.sql # 恢复 docker exec -i mysql-container sh -c exec mysql -uroot -proot_pass demo_db /data/backup/demo_db.sql更稳妥的方式是定期把容器数据卷里的文件打包备份备份数据放在宿主机之外的存储中。8. 容器化改造的最佳实践8.1 不要把容器当成虚拟机新手最容易犯的错误是把容器当成一台可以自由安装软件的小虚拟机。看到需要vim就apt install vim需要调试就长期挂着docker exec。容器的最佳实践是无状态、可随时销毁重建。所有需要持久化的数据写到数据卷配置通过环境变量注入日志输出到stdout。这样容器才能随时替换、随时迁移。8.2 镜像要小标签要清晰构建镜像时尽量选择轻量基础镜像比如alpine系列。少在容器里安装多余工具只保留运行所需的最小集合。镜像标签建议使用语义化版本号比如demo-app:v1.2.0不要每个构建都用latest。latest无法区分镜像内容回滚时容易出错。8.3 环境变量存配置不存密码应用配置中连接地址、端口、日志级别这类信息通过环境变量注入方便在不同环境dev/test/prod之间切换。但数据库密码、API密钥这类敏感信息不要直接写在docker-compose.yml或Dockerfile里建议使用Docker秘密机制或专门的密钥管理工具。任何时候代码仓库里都不应出现明文密码。8.4 生产环境部署必须考虑的几件事容器化改造上线后除了功能正常还要关注几个运维维度日志采集容器随时可能被销毁日志不能只留在容器内。需要对接统一的日志平台。监控告警用docker stats排查临时问题可以生产环境要有标准监控方案容器CPU、内存、重启次数都要纳入监控。数据备份数据库容器要定期备份备份结果要验证可恢复。资源限制生产环境建议给容器设置资源上限防止某个容器失控吃光整台服务器资源。常见做法是在docker run时加--memory和--cpus参数或在docker-compose.yml中配置mem_limit。权限最小化容器进程尽量不用root运行在Dockerfile里创建专用用户降低安全风险。8.5 团队协作中的镜像管理推荐将镜像推送到私有仓库比如Harbor而不是依赖每台服务器各自build。流程是CI流水线构建镜像推送私有仓库服务器从私有仓库拉取指定标签镜像并启动。这样镜像内容和服务器操作记录都可追溯回滚只需要把标签改回上一个版本即可。9. 从90分钟入门到Linux运维进阶这篇教程覆盖了Docker的完整主干安装、镜像、容器、数据卷、网络、docker-compose、项目部署和问题排查。90分钟内如果全部亲手操作一遍你已经具备把单机项目容器化部署的能力。从入门走向更深入的方向有几条路径值得继续探索容器编排Kubernetes是当前的工业级标准它的价值在于跨多台服务器管理容器。学Kubernetes之前Docker基础必须扎实因为Kubernetes的核心API对象Pod、Service、Deployment都和容器概念紧密关联。容器网络细节本文只用到了bridge网络下容器间通过容器名通信实际生产中还有宿主机网络、多节点跨机通信、Ingress流量管理这些更复杂的网络模型。CI/CD集成把镜像构建接入Jenkins或GitLab CI实现提交代码后自动构建镜像、自动部署到测试环境。镜像安全镜像不是越新越好要关注漏洞扫描、基础镜像更新、依赖升级。Harbor和Trivy都可以做镜像安全扫描。Linux运维这条线也很值得持续投入。Docker命令用多了之后你会发现它其实是Linux操作系统能力的封装。容器本质是Linux的Namespace隔离加上Cgroup资源限制Docker只是让这些能力好用起来。当你需要排查容器网络异常、定位容器内进程的CPU占用、理解容器磁盘占用时Linux命令功底决定了解题速度。这也是我建议每个学Docker的人同时补Linux基础的原因。两者不是并列关系而是基础与上层的关系。完成这篇教程里的操作之后下一步可以考虑系统学习Linux系统管理把进程管理、网络配置、用户权限、文件系统这些知识补齐。运维这条路没有天花板Docker只是第一块重要的里程碑。