1. 核心概念
Docer 是目前高效的软件部署技术,简单来说就是给应用程序封装独立的运行环境,每一个运行环境就是一个容器(Container) ,运行容器的计算机成为宿主机。
Docker 和虚拟机最大区别是:虚拟机需要完整的操作系统内核,而 Docker 共享宿主机的操作系统内核,所以 Docker 更轻量,资源占用少,启动更快。
Docker 还有一个更重要的概念是:镜像(Image) ,镜像是容器模版,可以把镜像类比成软件安装包,而容器是安装出来的软件。举一个现实世界的例子,镜像是食谱,容器就是根据食谱做出来的菜。可以使用一个菜谱,做出很多个菜,还可以把食谱分享给别人使用。
Docker 仓库(Docker Registry) 就是用来存放分享镜像的地方,每个人都可以把自己的镜像上传到仓库里面,然后其他人就可以下载镜像并且使用了。Docker 的官方仓库就是 Docker Hub,存储了许多人分享的镜像。
了解容器、镜像、镜像仓库这些概念就足够使用 Docker 了。
2. Docker 安装
2.1. windows 安装
- 任务栏搜索功能,启用"适用于Linux的Windows子系统" + "虚拟机平台"


- 管理员权限打开命令提示符,安装wsl2
css
wsl --set-default-version 2
wsl --update --web-download
- 下载Windows版本 desktop,进入此项目的Release
github.com/tech-shrimp...

如果想自己指定安装目录,可以使用命令行的方式 参数 --installation-dir=D:\Docker可以指定安装位置
sql
start /w "" "Docker Desktop Installer.exe" install --installation-dir=D:\Docker
2.2. mac 电脑安装
(macOS 从零安装 Docker 完全指南(含镜像加速与代理配置)macOS官网下载Docker Desktop)
2.3. Linux 服务器安装
3. 1.1 Linux
一键安装命令
arduino
sudo curl -fsSL https://get.docker.com| bash -s docker --mirror Aliyun
备用命令(每天自动从官网定时同步)
arduino
sudo curl -fsSL https://github.com/tech-shrimp/docker_installer/releases/download/latest/linux.sh| bash -s docker --mirror Aliyun
备用2(如果Github访问不了,可以使用Gitee的链接)
arduino
sudo curl -fsSL https://gitee.com/tech-shrimp/docker_installer/releases/download/latest/linux.sh| bash -s docker --mirror Aliyun
启动docker
sql
sudo service docker start
如果以上不太懂也可以看看我的其他文章的配置,有我的踩坑经历!
手把手教你用 Docker 部署 Vue 项目(含国内镜像加速 + 踩坑指南)
4. Docker 常用命令
介绍几个比较常用的 Docker 命令
4.1. docker pull(拉取/下载镜像)
从仓库下载 nginx
镜像(默认从 Docker Hub)。
bash
docker pull docker.io/library/nginx:latest

- docker.io -> registry:仓库,docker.io 代表是官方仓库,可以省略仓库地址。
- library -> namespace:命名空间(作者名),library 是官方仓库 的命名空间,可以省略。
- latest -> tag:标签(版本号),可以指定下载特定版本,省略代表下载最新版本。
简化后是下面这样:
docker pull nginx
4.2. docker images(查看镜像)
docker images 代表列出本地所有镜像。
docker images
4.3. docker rmi(删除镜像)
docker rmi 代表删除镜像,rm 是 remove 的缩写,i 是 image 的缩写。
bash
docker rmi nginx # 删除名字为nginx的镜像
docker rmi 1e5f3c4b56e # 删除id为1e5f3c4b56e的镜像
4.4. docker rm 删除容器
bash
docker rm nginx # 删除名字为nginx的容器
docker rm 1e5f3c4b56e # 删除id为1e5f3c4b56e的容器
介绍一个参数:-f(force)强制删除,如果删除正在运行的容器就需要加这个参数
bash
docker rm -f 1e5f3c4b56e
4.5. docker run(运行容器)
docker run 代表运行一个容器,这个命令是比较重要的
arduino
docker run nginx # 运行名字为nginx的镜像
docker run 1e5f3c4b56e # 运行id为1e5f3c4b56e的镜像
- 其实
docker pull
可以省略,直接执行docker run
,如果 docker 发现本地不存在镜像会先自动拉取一份,然后再创建并运行容器。
接下来看几个重要参数:
css
docker run -d --name web -p 8080:80 nginx
4.5.1. -d(后台运行)
detached 模式(后台运行),没有这个参数时,容器会在前台运行,终端会被"占用"。
4.5.2. --name(命名)
给容器起名字叫 web
- 如果不指定,Docker 会随机生成一个名字(比如
adoring_turing
)。 - 有名字后,可以用
docker stop web
、docker rm web
等来操作。 - 注意这个名字在整个宿主机上是唯一的
4.5.3. -p(端口映射)
端口映射
8080
→ 宿主机端口。80
→ 容器内部的端口(nginx 默认监听 80)。- 含义:访问宿主机
http://localhost:8080
就能访问到容器里的 nginx 服务。

Docker 容器的网络和宿主机是隔离的,宿主机访问不到容器(比如我在宿主机安装了一个 nginx 在浏览器输入 localhost:80,就能访问这个 ngxin 提供的网页,但是使用 docker 启动 nginx ,在浏览器 localhost:80 则无法访问对应网页)。用 -p 宿主机端口:容器端口
参数,就能把容器端口映射到宿主机端口,从而在宿主机通过浏览器访问到容器里的服务。

4.5.4. -v(挂载卷)
挂载卷(Volume)就是把宿主机的目录和容器内的目录绑定在一起,容器和宿主机对这个目录的修改会相互影响。它的最大作用是实现数据持久化,即使容器被删除,数据仍保存在宿主机上。

4.5.4.1. -v 宿主机目录:容器内目录(绑定挂载)
简单一句话记: "容器数据不随容器消失,宿主机和容器共享目录"。
xml
docker run -d -v <宿主机路径>:<容器路径> --name web nginx
接下来实战一下:
bash
docker run -d -p 80:80 -v /root/web:/usr/share/nginx/html nginx
此时出现 403 页面不要慌是因为 web 目录是空的被覆盖了
bash
cd web
vi index.html
然后粘贴下面代码,按下 esc 输入:wq!,访问 80 端口就能看到页面了
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Hello Docker Nginx</title>
</head>
<body>
<div class="box">
<h1>Hello Docker + Nginx</h1>
<p>这个页面是从 <code>/root/web/index.html</code> 提供的</p>
<p>容器挂载卷运行成功</p>
</div>
</body>
</html>
4.5.4.2. -v 卷的名字:容器内目录(命名卷挂载)
上面的例子直接把宿主机的目录写在了 docker run
命令里面,这种挂载方式叫做绑定挂载。还有另外一种挂载方式是让 Docker 自动创建啊一个存储空间,我们为这个存储空间起一个名字,然后挂载的时候直接使用名字叫可以了,这种挂载方式叫做命名卷挂载。
一句话总结:
- 绑定挂载:把宿主机指定目录和容器目录直接绑定,容器改动会实时影响宿主机目录。
- 命名卷挂载:由 Docker 管理的数据卷,不依赖宿主机目录,常用于持久化和共享数据。
arduino
docker run -d -p 宿主机端口:容器端口 -v 卷名:容器目录 镜像名
lua
docker volume create nginx_html
接下来实战一下
javascript
docker run -d -p 80:80 -v nginx_html:/usr/share/nginx/html nginx
- 这里就不需要写路径了,直接使用挂在卷的名字
查看挂载卷的详情信息
docker volume inspect nginx_html

bash
# 然后再进入这个文件修改index.html(注意这个文件路径只有linux系统可以使用,mac或者windows的存在 Docker 自己管理的 Linux 虚拟机里)
cd /var/lib/docker/volumes/nginx_html/_data
vi index.html # 然后把内容替换成上面的index.html,按下i编辑,按下esc,输入:wq!,再访问一下内容就变了

4.5.4.3. 关于挂载卷的一些命令
bash
docker volume list # 列出所有创建的卷
docker volume rm nginx_html # 删除卷
docker volume prune -a # 删除所有没有任何容器在使用的卷
这个就是挂载卷在宿主机的真实目录
4.5.5. -e(传递参数)
-e
常用场景是给容器传递配置参数,如数据库账号密码、应用端口、用户名等,让容器启动时自动初始化或配置环境。
ini
docker run -d \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=mydb \
--name my-mysql \
mysql:8
然后连接
css
mysql -h 49.233.249.191 -P 3306 -u root -p
填写自己宿主机的 ip 地址,如果出现 Welcome 就代表连接成功,如果连接不上要设置入站规则把 3306 端口打开。
4.5.6. -it(交互)和 --rm(容器停止时候删除容器)
-i:Interactive(交互式) ,保持容器的标准输入(stdin)打开,这样你可以在容器里输入命令进行交互。
--rm:容器停止后自动删除,用于临时容器,避免占用磁盘空间。
bash
docker run -it --rm ubuntu /bin/bash
- 启动一个临时 Ubuntu 容器
- 可以交互式操作
- 容器退出后自动删除
这种用法非常适合 调试、临时测试环境。
4.5.7. --restart(配置重启策略)
--restart
用来指定 容器退出后 Docker 是否自动重启容器,可以确保服务长期可用。
下面介绍两个比较常用的选项
4.5.7.1. always (总是重启)
ini
docker run -d --restart=always nginx
- 容器停止或 Docker 重启,nginx 容器会自动启动
4.5.7.2. unless-stopped
ini
docker run -d --restart=unless-stopped nginx
unless-stopped
表示 除非手动停止,否则容器会自动重启。
-
- 容器异常退出 → Docker 自动重启
- Docker 守护进程重启 → 容器自动重启
- 手动执行
docker stop
→ 容器不会再自动重启
4.6. docker ps(查看容器状态)
docker ps 代表查看容器状态,ps 就是 process status(进程状态)的缩写
docker ps
5. Docker 调试命令
5.1. docker inspect(查看信息)
docker inspect
用来 查看 Docker 对象的详细信息 ,返回结果是 JSON 格式,包括容器、镜像、网络、卷等的各种属性。
- 容器信息:IP、端口映射、挂载卷、环境变量、状态等
- 镜像信息:ID、标签、创建时间、大小、层结构
- 网络信息:子网、网关、关联容器
- 卷信息:挂载路径、创建时间、驱动类型
bash
docker ps nginx # 可以加名字或者id
5.2. docker create(创建容器)
docker create
用来 创建一个容器,但不启动它。
- 它只会在 Docker 中生成一个容器实例
- 不会像
docker run
那样立即启动容器,如果想启动需要输入docker start
bash
docker create nginx # 可以加名字或者id
5.3. docker logs(查看日志)
docker logs nginx -f
- -f 等于 --follow ,也就是追踪输出
5.4. docker exec (执行命令)
docker exec
用来 在已经运行的容器中执行命令 。 容器必须是 正在运行 的
bash
docker exec -it nginx
- -it:加上这个参数可以进入容器的交互式终端 , 可以在里面敲命令、查看文件、修改内容 , 类似直接在 Linux 系统里操作 。
6. Dockerfile
Dockerfile 是一个文本文件,用来 定义 Docker 镜像的构建过程
如果说菜是容器,镜像是食谱,那么详细的烹饪步骤和食谱说明书,告诉你怎么一步步做出镜像 。
在本地运行 Vue 项目时,我们通常先安装 Node,然后执行 npm install
下载依赖,再通过 npm run dev
启动项目。将这个流程打包成 Docker 镜像的原理类似,只需要通过编写 Dockerfile 就能实现自动化构建和运行。
- 首先要创建一个 Dockerfile 文件
bash
# 1. 使用 Node 镜像
FROM node:20-alpine # 相当于安装node环境依赖
# 2. 设置工作目录
WORKDIR /app
# 3. 复制 package.json 和 package-lock.json
COPY package*.json ./ # 需要安装的依赖
# 4. 安装依赖
RUN npm install
# 5. 复制项目文件
COPY . .
# 6. 暴露开发服务器端口
EXPOSE 5173
# 7. 启动 Vue 开发服务器
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
- Dockerfile 准备好之后就可以构建镜像了,进入到项目根路径
bash
docker build -t docker_demo .
# -t意思是给镜像起一个名字,.的意思是在当前文件夹构建
- 镜像构建好了之后,可以使用
docker run
命令基于这个镜像创建一个容器并且运行起来。
yaml
docker run -d -p 5173:5173 docker_demo
# -d是后台运行容器,-p是端口映射,docker_demo是镜像的名字
然后在浏览器输入http://localhost:5173/就可以看到了
7. 上传 Docker Hub
首先要去官网注册 Docker Hub,然后再使用命令登录
docker login
推送镜像的时候必须在前面加上作者的用户名,所以现在重新打一个镜像
bash
docker build -t mingli03/docker_demo .
接下来 push 自己的镜像
bash
docker push mingli03/docker_demo

docker.io 是仓库的地址,mingli03 是命名空间,docker_demo 是镜像名/
推送成功之后,在 docker hub 里面输入 命名空间/镜像名 就能搜索到镜像了,其他人使用 docker pull 就可以拉取镜像了
bash
docker pull mingli03/docker_demo

8. Docker 网络
Docker 网络是容器之间以及容器与外部世界通信的基础。可以把它理解为 容器的虚拟网络环境。
8.1. Docker 网络的作用
- 让 同一宿主机的容器互相通信
- 让 容器访问外部网络(如互联网)
- 控制容器之间的 隔离和访问权限
- 支持 多主机容器互联(Docker Swarm/Overlay 网络)
8.2. Docker 默认网络类型
Docker 创建容器时,如果不指定网络,会自动连接到 默认网络(bridge),主要有三类:
网络类型 | 描述 |
---|---|
bridge | 默认网络,容器与宿主机隔离,但同一桥接网络的容器可以互通 |
host | 容器共享宿主机网络,端口直接映射到宿主机 |
none | 容器没有网络 |
8.3. bridge 模式
Docker 默认使用 bridge(桥接)网络 ,所有容器都会分配一个内部 IP(通常以 172.17 开头)。在同一网络内,容器可以通过 IP 或容器名互相访问,但容器网络与宿主机网络相互隔离。通过 docker network create
可以创建自定义子网(也是桥接模式),同一子网内的容器可以互通,跨子网容器默认无法通信,同时可以直接用容器名访问而无需记 IP。
- 创建自定义网络
lua
docker network create --driver bridge my-net
my-net
→ 网络名称--driver bridge
→ 桥接模式
- 运行两个容器加入同一网络
css
# 运行 Nginx 容器
docker run -d --name web --network my-net nginx
# 运行 BusyBox 容器,用来测试网络
docker run -it --name tester --network my-net busybox
- 测试容器互通
在 tester
容器里执行:
arduino
# ping web 容器
ping web

说明 同一子网内,容器可以通过名字互相通信。
8.4. host 模式
Docker 容器直接共享宿主机的网络,容器直接使用宿主机的 IP 地址,而且无需 -p 进行端口映射,容器内的服务直接运行在宿主机的端口上,通过宿主机的 IP 和端口就能访问到容器。
arduino
docker run -d --network host nginx

8.5. docker network list(查看网络)
展示出所有 Docker 网络
docker network list

这个 my-net 就是刚刚创建的自定义网络
8.6. docker network rm(删除网络)
bash
docker network rm 713a88f03530
- 默认的三种网络模式不能删除,只能删除自定义网络。
- 这里删除了刚刚自定义的网络,再执行 list 就查看不到这个网络了。
9. Docker Compose
9.1. 概念
在实际开发中,一个完整的应用往往由多个部分组成,比如前端、后端和数据库。
如果我们把所有模块都打包进一个"大容器",表面上看似简单,但会带来严重问题:
- 可靠性差:一旦某个模块(如后端内存泄漏)出问题,整个大容器都可能崩溃。
- 扩展性差:无法单独扩容某个模块,比如想多开几个数据库实例,只能把整个大容器再复制一份,既浪费资源又不灵活。
因此最佳实践是将每个模块单独容器化,形成多个容器(前端容器、后端容器、数据库容器等),这样既能独立维护,也能灵活扩展。
但问题来了:
- 用
docker run
创建多个容器时,需要写多条命令。 - 容器之间的网络、依赖、挂载卷等需要手动配置。
- 容器数量一多,管理起来就很容易出错。
这时候就需要 容器编排工具 ------ Docker Compose。
Docker Compose 使用一个 docker-compose.yml
文件,把多个容器的定义和它们之间的协作方式写进去。它可以看作是把多条 docker run
命令写成一个清晰的配置文件。通过一条命令:
docker compose up -d
就能把整个应用(前端 + 后端 + 数据库)同时启动,并自动加入同一个网络。
9.2. 实战
新创建一个文件夹
docker-bushu/
│
├─ docker-compose.yml
├─ backend/
│ └─ app.jar
├─ frontend/
│ ├─ dist/
│ └─ nginx.conf
└─ mysql_data/ (存放初始化数据库文件或持久化数据)

- nginx.conf
ini
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
- docker-compose.yml
yaml
version: "3.9"
services:
frontend:
image: nginx:alpine
container_name: my-frontend
ports:
- "8080:80"
volumes:
- ./frontend/dist:/usr/share/nginx/html
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- backend
backend:
image: openjdk:17-jdk-alpine
container_name: my-backend
volumes:
- ./backend/app.jar:/app/app.jar
command: ["java", "-jar", "/app/app.jar"]
ports:
- "8081:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: 123456
depends_on:
- db
db:
image: mysql:8
container_name: my-mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mydb
volumes:
- ./sql:/docker-entrypoint-initdb.d
ports:
- "3306:3306"
- vite.config.js
javascript
server: {
proxy: {
"/api": {
target: "http://localhost:8080", // 后端接口地址
changeOrigin: true, // 修改请求头中的 origin
rewrite: path => path.replace(/^/api/, ""), // 去掉前缀 /api
},
},
},
- 前端请求路径
javascript
axios.post("/api/user/register", form).then((res) => {
console.log("注册成功:", res);
});
- 请求忽略(WebConfig)
typescript
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 正确的配置方式
registry.addInterceptor(loginInterceptor)
// 1. 先指定拦截所有路径
.addPathPatterns("/**")
// 2. 然后排除不需要拦截的路径
.excludePathPatterns(
"/user/login",
"/user/register"
);
}
}
- 请求路径
less
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public Result register(@RequestBody @Valid User user) {
// 校验通过才会进入这个方法体
// 否则会抛出 MethodArgumentNotValidException 异常
// 1. 查找用户名字是否一样
User existingUser = userService.findByUsername(user.getUsername());
if (existingUser == null) {
// 不一样就添加
userService.register(user);
return Result.success();
} else {
return Result.error("用户名已存在");
}
}
}
然后把这个文件夹传递到服务器上
docker compose up -d
如果前端或后端更新了:
- 停止对应容器:
arduino
docker compose stop frontend
-
替换
dist
或app.jar
文件 -
再启动容器:
docker compose up -d frontend