Docker 入门:把应用装进盒子里
以前部署项目总要手动装各种环境,换台机器就报错。学了 Docker 之后发现,把应用和依赖打包在一起,到哪都能跑。
Docker 入门:把应用装进盒子里
之前部署项目的时候,遇到过这样一个问题:
本地跑得好好的,放到服务器上就跑不起来。
问了原因,发现是环境不一样。
本地 Java 17,服务器上 Java 11;本地 MySQL 8.0,服务器上 MySQL 5.7;还有的库版本对不上,报各种错。
折腾了好久才搞定,后来听说有 Docker 这种东西,才解决了这个问题。
Docker 到底是什么
Docker 是一种容器化技术,核心思想很简单:
把应用和它运行所需的一切(代码、依赖、运行时、配置)打包成一个镜像,然后从这个镜像运行出容器。
代码 + 依赖 + 运行时 + 配置 → 镜像(只读的模板)
镜像 → 运行 → 容器(正在运行的实例)
镜像是静态的模板,容器是动态的运行实例。
一个镜像可以跑多个容器,每个容器之间互相隔离。
核心价值就一句话:"一次构建,到处运行"。
为什么需要 Docker
没有 Docker 的时候,部署项目要经历什么:
- 换台服务器,先装系统
- 装 Java、MySQL、Redis、Nginx
- 配置环境变量、防火墙、路径
- 部署应用,调各种配置
每一步都可能出问题,而且不同环境配置还不一样。
Docker 解决的是什么:
- 镜像把环境打包在一起,到哪都一样
- 多个容器可以跑不同版本,互不干扰
- 一条命令启动,秒级拉起
- 容器极轻量,一台服务器可以跑几十个
Docker vs 虚拟机
有人可能会问,这不是和虚拟机差不多吗?
确实有相似之处,但关键区别很大:
| 对比项 | 虚拟机 | Docker 容器 |
|---|---|---|
| 重量 | 重(GB 级) | 轻(MB 级) |
| 启动时间 | 分钟级 | 秒级 |
| 隔离性 | 完整 OS 隔离 | 进程级隔离 |
| 资源占用 | 每个 VM 独占内核 | 共享宿主机内核 |
| 磁盘占用 | 大 | 小 |
简单理解:虚拟机是「一台完整的电脑」,容器是「一个进程被隔离起来了」。
核心概念三件套
学 Docker 记住三个词就够了:
镜像(Image) — 只读的模板,类似「类」 容器(Container) — 镜像的运行实例,类似「对象」 仓库(Registry) — 存放镜像的地方,类似「代码仓库」
最常见的仓库是 Docker Hub(docker.io),里面有各种官方镜像。
常用命令
镜像操作
docker images # 查看本地镜像
docker pull nginx # 拉取镜像
docker rmi nginx # 删除镜像
容器操作
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(含停止的)
docker run nginx # 运行容器
docker start/stop/restart <容器名> # 控制容器状态
docker rm <容器名> # 删除容器
docker exec -it <容器名> bash # 进入容器内部
docker logs <容器名> # 查看容器日志
一个完整的例子
# 拉取并运行 Nginx,映射 80 端口
docker run -d --name my-nginx -p 80:80 nginx
# 查看运行状态
docker ps
# 进入容器
docker exec -it my-nginx bash
# 查看日志
docker logs my-nginx
# 停止并删除
docker stop my-nginx
docker rm my-nginx
Dockerfile:自定义镜像
官方镜像不够用的时候,就需要自己写 Dockerfile。
Dockerfile 是一个文本文件,每一行是一个指令,Docker 按顺序执行这些指令来构建镜像。
打包 Spring Boot 应用
# 编译阶段
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar", "--spring.profiles.active=prod"]
这里用了多阶段构建,第一阶段编译(用完整版 Maven),第二阶段运行(用精简 JRE)。
好处是最终镜像体积很小,从 800MB 降到 200MB 左右。
打包 Vue 前端
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
RUN npm run build
# 运行阶段
FROM nginx:1.24-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Docker Compose:多容器编排
一个项目通常不止一个容器,可能有 MySQL、Redis、后端、前端四个。
一个个 docker run 太麻烦了,Docker Compose 就是来解决这个问题的。
用一个 YAML 文件定义所有容器及其关系,一条命令启动:
services:
mysql:
image: mysql:8.0
container_name: myapp-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: Root@123456
MYSQL_DATABASE: myapp
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-net
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: always
volumes:
- redis-data:/data
networks:
- app-net
backend:
build: ./backend
container_name: myapp-backend
restart: always
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
REDIS_HOST: redis
depends_on:
- mysql
- redis
networks:
- app-net
frontend:
image: nginx:1.24-alpine
container_name: myapp-frontend
restart: always
ports:
- "80:80"
volumes:
- ./frontend/dist:/usr/share/nginx/html:ro
networks:
- app-net
depends_on:
- backend
networks:
app-net:
driver: bridge
volumes:
mysql-data:
redis-data:
然后一条命令启动所有服务:
docker compose up -d
关闭:
docker compose down
服务间怎么通信
Docker Compose 会自动为每个服务创建 DNS 记录,服务名就是主机名。
所以后端访问 MySQL,不需要写 IP,直接写服务名:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/myapp
mysql 就是 Compose 里定义的 service 名字,Docker 会自动解析。
数据持久化
容器删了数据会丢失,所以要挂数据卷:
volumes:
- mysql-data:/var/lib/mysql
或者绑定挂载:
volumes:
- ./data/mysql:/var/lib/mysql
第一种由 Docker 管理,第二种直接映射到宿主机目录,更方便备份。
常用命令汇总
# 构建并启动
docker compose up -d
# 查看状态
docker compose ps
# 查看日志
docker compose logs -f
# 停止所有服务
docker compose down
# 销毁容器和数据卷(⚠️ 数据会丢失)
docker compose down -v
一句话总结
Docker 的核心就三件事:
镜像打包环境,容器运行应用,Compose 编排多容器。
学完 Docker 之后,部署项目终于不用手动装环境了。
项目换个服务器,只需要复制镜像和 Compose 文件,一条命令搞定。
这种"把所有问题都解决在盒子里"的感觉,还挺爽的。