一文看懂 Docker 究竟是个啥
目录
前言
新手学习开发、项目部署时,几乎100%都会遇到一个无解的难题:项目在我电脑上完美运行,换一台电脑、发给同事、部署到服务器就直接报错。
很多人第一反应会疑惑:这到底是为什么?单纯是软件版本不匹配吗?
其实不只是版本问题,本质是每一台电脑的运行环境都是独一无二的:
-
本机和他人电脑系统不同(Windows / Mac / Linux)
-
编程语言版本不一致(Python3.9 / Python3.10、JDK8 / JDK17)
-
项目依赖库缺失、版本冲突
-
环境变量、系统配置差异
传统开发模式下,想要让项目在另一台电脑跑起来,需要手动配齐所有环境、安装依赖、调试配置,耗时费力,还极易出现新的报错。而 Docker 的诞生,就是为了彻底根治「本地能跑、别处报错」的环境兼容难题。
Docker的核心设计思想
用一个生活化比喻:
项目 = 一盆花
Docker = 可独立移植的完整花盆
Dockerfile = 标准化种花、养花盆的说明书
他人/运营电脑 = 任意一块不确定的空地(沙漠、石头地、普通泥地)
- 传统运行方式的弊端:
你精心养护的花(项目),在自家阳台(本地电脑)温度、土壤、水肥都适配,长势很好。但如果直接把花移栽到别人的空地(其他电脑),土壤不对、温度不符、缺少肥料,花大概率直接枯萎、死掉,对应就是项目报错、无法运行。
- Docker的核心解决方案:
不单独移栽花,而是把「花+土壤+肥料+适宜温度」整套环境打包成一个独立花盆。
无论把这个花盆放到任何空地(任何电脑、服务器),不需要适配环境、不需要额外养护,放下就能活,完美实现 一次打包,处处运行。
一、Docker
三大概念
仓库(Repository)→ 镜像(Image)→ 容器(Container)
仓库中存储着镜像,镜像实例化后生成容器。


核心原理
Docker 核心基于 Linux Namespace 做环境隔离、Cgroups 做资源限制,
结合分层镜像机制实现轻量化虚拟化。
容器共享宿主机内核,资源占用小、启动快、可移植性强,
通过统一封装应用与依赖,解决多环境运行不一致问题,实现一次打包、处处运行。
有什么用
不止服务开发,全员受益!
1. 解决开发协作痛点
团队开发中,不用再统一要求所有人安装相同版本的语言、依赖、中间件,避免因本地环境差异导致的协作冲突,统一项目运行标准。
2. 适配非技术人员使用
运营、产品、测试等非开发岗位的电脑,没有配置任何代码运行环境。传统方式完全无法启动项目,而搭配 Docker 后,只需安装Docker软件,就能一键启动完整项目,无需任何环境配置、无需代码基础。
3. 适配服务器部署
服务器无需配置复杂运行环境,杜绝线上线下环境不一致问题,大幅降低部署故障、运维成本。
操作命令
1. 镜像的操作

2. 容器的操作

创建容器
停止容器,查看后发现没有了(docker ps 默认只展示没有停止的)
docker ps -a (可以展示运行中和停止的镜像)
删除容器:(docker rm 不能删除运行中的容器,除非加上 -f 参数强制删除)
二、数据卷(容器数据持久化)
是什么
数据卷,是一个虚拟目录,通常指向宿主机上的某个目录,这个目录是由Docker虚拟化管理的,它可能位于宿主机的文件系统内部的一个特定区域。
当Docker守护进程运行时,它会管理所有数据卷的生命周期,包括它们的创建、删除和使用。这意味着,即使宿主机上的对应目录被删除,Docker仍然能够控制数据卷的内容和状态。
翻译一下就是:
数据卷 = 容器的 “外置 U 盘” (把容器里的文件夹,链接到你电脑真实的文件夹上)

有什么用
容器有个致命问题: 容器删了 → 里面的数据也跟着删了!
比如:
- 你在容器里运行 MySQL,存了 1000 条数据
- 你把容器删了
- 数据全部消失,找不回来!
为什么? 因为容器本身是临时的、一次性的
数据卷的作用:让数据不跟着容器一起死!
容器可以随便删、随便重建、随便更新 但数据永久保存在宿主机(你的电脑 / 服务器)上。
容器和数据耦合的问题

使用数据卷的好处包括:
- 持久化数据:即使容器停止或删除,数据卷中的数据还在。
- 数据共享:本地 ←→ 容器互通。多个容器可以挂载同一个数据卷,共享数据。
- 隔离应用和数据:数据卷使得应用和数据分离,便于备份和迁移,独立于容器生命周期。
操作命令

创建数据卷:
查看数据卷:
删除数据卷:
挂载数据卷:
docker run -v /my/volume:/path/in/container -d my_image
上面的命令将宿主机上的/my/volume目录挂载到容器中的/path/in/container路径。这并不意味着/my/volume是容器内部的一个真实目录,而是Docker为这个挂载点创建了一个数据卷,并且这个数据卷在宿主机上有一个特定的存储位置。
三、Dockerfile (放在微服务项目)
是什么
比如你要想运行你的微服务的docker实例(user/order/gateway):
需要:先写 Dockerfile → build 镜像 → 运行实例
如果要想运行中间件(MySQL/Redis/Nacos),直接:从仓库拉取下载 → 运行镜像。
( 此时不需要自己写 Dockerfile,相当于从仓库直接拉取下来了镜像
官方 / 维护者 自己写了 Dockerfile → 本地 docker build → 生成镜像 → docker push 到仓库。)
Docker 实现环境打包的核心,就是 Dockerfile —— 它是告诉Docker如何构建项目运行环境的配置文件,所有步骤都是标准化、可复用的。
流程:
Dockerfile → build → 镜像 → push → 仓库 → pull → 镜像 → run → 容器
以Python Flask项目为例,极简基础版Dockerfile如下:
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install flask
CMD ["python", "app.py"]
逐行通俗解析:
-
FROM python:3.9:指定基础运行环境。相当于提前备好一台预装Python3.9的干净设备,容器初始是空环境,必须依托官方基础镜像提供运行所需的底层环境。
-
WORKDIR /app:在容器内创建并指定项目工作目录,后续所有操作都在这个目录下执行,统一项目路径,避免文件混乱。
-
COPY . .:将本机当前文件夹下的所有项目代码、配置文件,全部复制到容器的 /app 目录中,让容器可以读取到项目源码。
-
RUN pip install flask:在容器环境内安装项目所需的第三方依赖包,补齐项目运行必备的依赖环境。
-
CMD ["python", "app.py"]:指定容器启动后的执行命令,也就是项目的启动指令,让容器启动后自动运行项目。
为什么必须要有这几步?底层设计逻辑
很多人只会抄写Dockerfile,但不懂底层逻辑。其实所有Dockerfile的编写逻辑,
都围绕「项目能正常启动的四大必备条件」展开,缺一不可:
1. 底层运行环境(FROM)
2. 项目代码与运行目录(WORKDIR + COPY)
3. 第三方依赖(RUN)
4. 项目启动入口(CMD / ENTRYPOINT)
核心总结:环境 + 代码 + 依赖 + 启动命令 = 项目完整运行条件,这也是所有Dockerfile的核心设计逻辑。
操作命令
很多同学学完 Dockerfile 依然懵:我到底从哪一步开始用?完整流程是什么?
这里给大家梳理零基础、标准、完整的 Docker 使用四步曲,适配所有项目,结合花盆比喻全程通透:
第一步:编写 Dockerfile
根据项目类型(Python/Java/前端)编写专属构建文件,定义好「环境、代码、依赖、启动命令」,也就是我们上面讲到的项目运行四大必备条件。
第二步:docker build
执行构建命令,让 Docker 按照说明书,打包生成完整项目镜像。
docker build -t my-app:1.0 .
含义:在当前目录,根据Dockerfile构建镜像,命名为 my-app:1.0。
第三步:docker run
基于镜像启动容器,真正运行项目,对外暴露端口可以访问。
docker run -d -p 5000:5000 --name my-project my-app:1.0
-d 后台运行、-p 端口映射、--name 给容器命名,方便后续管理。
第四步:日常容器管理(养护/关停花盆)
项目运行后,使用简单命令管理容器,无需复杂操作。
企业级进阶 Dockerfile
上面的极简版适合学习、本地测试,而真实公司生产环境中,为了保证镜像安全、体积小、启动快、不泄露源码,会使用多阶段构建,以SpringCloud微服务项目为例,这是企业通用标准写法:
// ==== 第一阶段:构建阶段(仅用于打包项目,不参与最终运行)====
FROM maven:3.8.6-openjdk-17 AS builder
WORKDIR /app
// 优先复制pom文件,利用docker缓存加速构建
COPY pom.xml .
RUN mvn dependency:go-offline -B
// 复制源码并打包
COPY src ./src
RUN mvn clean package -DskipTests
// ==== 第二阶段:运行阶段(最终生产镜像,轻量、安全)====
FROM eclipse-temurin:17-jre-alpine
// 设置时区,解决日志、数据库时间偏差问题
ENV TZ=Asia/Shanghai
WORKDIR /app
// 仅复制打包好的jar包,不复制源码,保障代码安全
COPY --from=builder /app/target/*.jar app.jar
// 项目启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]
四、从镜像到容器
上面我们已经知道了如何通过微服务项目代码中的 dockerfile 生成镜像,接下来思考:
🤔: 生成的镜像最终如何在我们的服务器中形成容器的?
假设现在你的架构:
- 服务器 1:注册中心 + 网关
- 服务器 2:业务微服务(user-service、order-service)
- 服务器 3:数据库 + 缓存
方式1: 直接在服务器生成
方式2:将本地镜像上传到服务器
【项目代码】
↓
【Dockerfile】(在项目里)
↓
-----------------------------------
方式1:服务器 build → 服务器镜像
方式2:本地 build 镜像 → 传镜像文件 → 服务器镜像
-----------------------------------
↓
【服务器上的镜像】
↓
【docker-compose.yml】(在服务器上)
↓
【docker-compose up -d】
↓
【自动生成容器】
↓
【服务运行成功】
五、docker-compose.yml (放在服务器)
架构依然是:
- 服务器 1:注册中心 + 网关
- 服务器 2:业务微服务(user-service、order-service)
- 服务器 3:数据库 + 缓存
如果按照之前手敲命令:docker run → 启动一个服务,你要手动敲 6 条超长命令:
docker run nacos...
docker run mysql...
docker run redis...
docker run gateway...
docker run -d --name user-service user-service:1.0
...
现在:docker-compose → 一台机器启动一套服务 (将来:K8s → 管理多台机器的所有服务)
以服务器2举例:
文件名:docker-compose.yml
version: '3.8'
services:
# 用户服务
user-service:
image: user-service:1.0
container_name: user-service
restart: always
# 订单服务
order-service:
image: order-service:1.0
container_name: order-service
restart: always
最终只需要,单独运行命令:
docker-compose up -d
公式:
services:
abc123: # 随便写
image: user-service:1.0 # 必须和build一致,之前运行:docker build -t user-service:1.0 .
container_name: my-user # 随便写
restart: always
另外需要注意,docker-compose 只是帮我们省去了手动 docker run,但是 run 之前的 build 步骤还是依旧不变的,该 build 几次还是几次,本例子中需 build 3次,命令:
# 网关
docker build -t gateway:1.0 .
# 用户服务
docker build -t user-service:1.0 .
# 订单服务
docker build -t order-service:1.0 .
更多推荐




所有评论(0)