Java应用Docker化全指南:从打包到一键部署(阿里云ACR + 自建Harbor)
前言
把Java应用跑起来很简单——本地装JDK,命令行跑java -jar app.jar,齐活。
但生产环境完全不是这么回事。一台服务器上可能跑着十几个不同语言、不同版本、不同依赖的应用,它们之间互相"抢地盘"(端口冲突、库版本冲突、环境变量冲突),运维人员每次部署都像拆炸弹。
Docker 解决的就是这个问题——把应用和它的整个运行环境(操作系统、JDK、依赖库、配置文件)一起打包成一个镜像,镜像在任何装了Docker的机器上都能原封不动地运行。
这篇文章,我用一个最常见的Spring Boot应用做例子,覆盖完整的实战流程:
1
2
3
4
5
|
本地写Java代码
→ 打包成Docker镜像
→ 推送到阿里云ACR容器镜像仓库
→ 或推送到自建Harbor仓库
→ 服务器上一条命令拉取并运行
|
无论你用哪种仓库,最后部署的命令都是一样的。
一、准备工作:环境确认
1.1 需要安装的软件
在开始之前,确保你的机器上安装了以下软件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 检查 Docker 是否安装(版本 >= 18.09)
docker --version
# Docker version 26.1.3, build 97e0e27
# 检查 Docker 是否正常运行
docker ps
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# (无输出表示正常运行,空容器列表)
# 检查 Maven 或 Gradle(根据你的项目选择)
mvn --version
# Apache Maven 3.9.6 ...
# 或检查 Gradle
gradle --version
|
如果没有安装,以下是各系统安装方式:
Windows / macOS:下载 Docker Desktop 安装包,一键安装。
Linux(Ubuntu/Debian):
1
2
3
4
5
6
|
# 一行命令安装 Docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# 当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER
newgrp docker
|
Linux(CentOS/RHEL):
1
2
3
4
|
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER
|
1.2 一个简单的Spring Boot应用
为了演示,我准备了一个最简化的Spring Boot REST API项目。项目结构如下:
1
2
3
4
5
6
7
8
9
10
|
demo-app/
├── pom.xml
├── src/
│ └── main/
│ ├── java/com/example/demo/
│ │ ├── DemoApplication.java
│ │ └── HelloController.java
│ └── resources/
│ └── application.yml
└── Dockerfile
|
pom.xml(Maven配置):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo-app</artifactId>
<version>1.0.0</version>
<name>demo-app</name>
<description>Docker Demo Spring Boot Application</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
|
DemoApplication.java(启动类):
1
2
3
4
5
6
7
8
9
10
11
|
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
|
HelloController.java(REST接口):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.example.demo;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Value;
@RestController
@RequestMapping("/api")
public class HelloController {
@Value("${app.version:1.0.0}")
private String version;
@GetMapping("/hello")
public String hello() {
return "Hello from Docker! Version: " + version;
}
@GetMapping("/health")
public String health() {
return "OK";
}
}
|
application.yml:
1
2
3
4
5
6
7
8
9
|
server:
port: 8080
app:
version: "1.0.0"
spring:
application:
name: demo-app
|
先在本地确认应用能跑通:
1
2
3
|
cd demo-app
mvn clean package -DskipTests
java -jar target/demo-app-1.0.0.jar
|
浏览器访问 http://localhost:8080/api/hello,看到 Hello from Docker! Version: 1.0.0 即成功。
二、编写Dockerfile:多阶段构建
2.1 最简单的Dockerfile
Dockerfile 是构建镜像的"配方"。先看一个最基础的版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 基础镜像:包含 JDK 17
FROM eclipse-temurin:17-jre
# 工作目录
WORKDIR /app
# 复制打包好的 jar 文件
COPY target/demo-app-1.0.0.jar app.jar
# 暴露端口
EXPOSE 8080
# 启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 在项目根目录构建镜像
docker build -t demo-app:1.0.0 .
# 查看镜像
docker images | grep demo-app
# REPOSITORY TAG IMAGE ID CREATED SIZE
# demo-app 1.0.0 a3f8e2c1d9b4 10秒前 234MB
# 本地运行测试
docker run -d --name demo-test -p 8080:8080 demo-app:1.0.0
curl http://localhost:8080/api/hello
# Hello from Docker! Version: 1.0.0
# 清理测试容器
docker stop demo-test && docker rm demo-test
|
这个 Dockerfile 可以工作,但它有几个问题:
- 镜像体积大:包含了整个 JRE(Java Runtime),而
eclipse-temurin:17-jre 是精简版,已经比较小了,但如果用 17-jdk 会更大
- 没有分离源码和产物:源码被留在了镜像里(通过
COPY . . 会带入)
- 每次重新构建整个镜像:没有利用 Docker 的层缓存,代码改一行就要重装依赖
2.2 多阶段构建:标准生产实践
多阶段构建(Multi-Stage Build)是生产环境的标准做法。它用两个 FROM 指令,第一阶段负责编译打包,第二阶段只复制编译产物:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
# ============================================
# 阶段一:构建阶段(Maven/JDK 环境)
# ============================================
# 使用 Maven 官方镜像,包含 Maven + JDK 17
FROM maven:3.9-eclipse-temurin-17 AS builder
# 设置工作目录
WORKDIR /build
# 先复制 pom.xml(利用 Docker 层缓存:依赖不会每次重新下载)
COPY pom.xml .
# 下载依赖(只在 pom.xml 变化时重新执行)
RUN mvn dependency:go-offline -B
# 复制源码
COPY src ./src
# 打包(跳过测试,加快构建)
RUN mvn clean package -DskipTests
# ============================================
# 阶段二:运行阶段(只保留 JRE 和 jar 文件)
# ============================================
# 使用 JRE 镜像(比 JDK 镜像小很多)
FROM eclipse-temurin:17-jre AS runtime
WORKDIR /app
# 从构建阶段复制打包好的 jar 文件
COPY --from=builder /build/target/demo-app-1.0.0.jar app.jar
# 运行用户(非 root)运行应用(安全最佳实践)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -q --spider http://localhost:8080/api/health || exit 1
# 启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]
|
多阶段构建的优势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
传统方式(单阶段):
源码 → Maven 编译 → 所有依赖 + JDK + 源码 → 镜像(~500MB)
↑
包含编译工具、Maven、源码(暴露安全风险)
多阶段构建(两阶段):
阶段一:源码 → Maven 编译 → 输出 jar(~200MB 构建容器)
阶段二:只复制 jar + JRE → 最终镜像(~234MB)
最终镜像只包含运行时需要的东西,不包含:
- Maven 工具
- 编译依赖(node_modules、.m2 缓存等)
- 源代码
- JDK 开发工具
|
2.3 .dockerignore:避免复制不需要的文件
在项目根目录创建 .dockerignore 文件,排除不需要打包进镜像的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# Git 版本控制
.git
.gitignore
# Maven 打包产物(不需要,构建时重新生成)
target/
# IDE 配置
.idea/
*.iml
.vscode/
# 构建日志
*.log
logs/
# 测试文件
*Test.java
*Tests.java
src/test/
# 其他
README.md
Dockerfile*
docker-compose*
*.md
|
这和 .gitignore 的逻辑完全一样,目的是减少 COPY 指令传输的文件量,加快构建速度。
2.4 Jib:不用写Dockerfile的构建方式
如果你连 Dockerfile 都不想写,Google Jib 是另一个优秀的选择——它直接集成到 Maven/Gradle 中,用插件构建镜像:
Maven 方式(只需在 pom.xml 添加插件):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.3</version>
<configuration>
<from>
<image>eclipse-temurin:17-jre</image>
</from>
<to>
<!-- 镜像地址,构建后直接推到仓库 -->
<image>registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app</image>
<tags>
<tag>1.0.0</tag>
<tag>latest</tag>
</tags>
</to>
<container>
<jvmFlags>
<jvmFlag>-Xms256m</jvmFlag>
<jvmFlag>-Xmx512m</jvmFlag>
</jvmFlags>
<ports>
<port>8080</port>
</ports>
</container>
</configuration>
</plugin>
|
1
2
3
4
5
|
# 一条命令构建并推送到远程仓库
mvn compile jib:build
# 或者只构建本地镜像(不推送)
mvn compile jib:dockerBuild
|
Jib 自动处理多阶段构建、层拆分(按依赖 / 资源 / 类分层),构建出的镜像支持 Docker 层缓存,部署时只下载变更的层,极大加快部署速度。
三、镜像标签与版本管理策略
镜像标签(Tag)不是简单的版本号,它反映了镜像的来源、版本和用途:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 常用标签命名规范
镜像名:版本号
镜像名:latest # 始终指向最新版本(不推荐生产使用)
镜像名:1.0.0 # 语义化版本
镜像名:1.0.0-hotfix # 带后缀的版本
镜像名:20260718 # 日期版本
镜像名:git-abcd1234 # Git commit SHA(CI/CD 自动生成)
# 示例
demo-app:1.0.0 # 正式版本
demo-app:1.0.1-beta # 测试版本
demo-app:1.0.1 # 最新修复版本
|
语义化版本(Semantic Versioning):
1
2
3
4
5
6
7
8
9
10
11
12
|
版本格式:主版本.次版本.修订版本
1.0.0 → 首次发布
1.0.1 → 修复 Bug,不改功能
1.1.0 → 新增功能,向后兼容
2.0.0 → 破坏性变更,不兼容旧版本
常见后缀:
-alpha:内部测试版
-beta:公开测试版
-rc:候选发布版(Release Candidate)
-stable:稳定版
|
四、推送到阿里云容器镜像服务ACR
4.1 阿里云ACR简介
阿里云容器镜像服务(Container Registry,简称ACR)是阿里云提供的企业级Docker镜像仓库服务,支持容器镜像和Helm Chart的托管、推送、拉取。
ACR的两个版本:
| 版本 |
说明 |
费用 |
| ACR 个人版 |
基础功能,单个仓库,500MB存储 |
免费 |
| ACR 企业版 |
企业级,高可用,VPC内网访问,镜像安全扫描 |
按量付费/包年 |
4.2 创建镜像仓库
第一步:开通阿里云ACR服务
登录阿里云控制台 → 搜索"容器镜像服务" → 点击"创建实例"(按提示操作)。
第二步:创建命名空间
命名空间(Namespace)是仓库的分组,类似Maven的groupId:
1
2
3
4
5
6
|
命名空间格式建议:
公司域名反写 + 产品名
com.example.demo
或
团队名-项目名
myteam-app
|
第三步:创建镜像仓库
1
2
3
4
|
仓库名称:demo-app
命名空间:your-namespace
仓库类型:私有
代码源:不导入代码(手动推送)
|
创建完成后,你会看到仓库地址,格式为:
1
|
registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app
|
4.3 配置凭证并推送
第一步:获取Registry密码
阿里云控制台 → 容器镜像服务 → 访问凭证 → 设置独立登录密码。
第二步:Docker登录
1
2
3
4
5
6
7
8
|
# 方式一:命令行登录(交互式)
docker login registry.cn-hangzhou.aliyuncs.com
# Username: your-namespace
# Password: ********
# Login Succeeded
# 方式二:非交互式登录(CI/CD场景)
echo "your-password" | docker login registry.cn-hangzhou.aliyuncs.com -u your-namespace --password-stdin
|
注意:阿里云ACR支持使用阿里云AccessKey登录,不需要单独创建Docker Registry密码。也可以在容器镜像服务控制台的"访问凭证"中生成专用的Registry密码。
第三步:给镜像打标签
1
2
3
4
|
# 镜像ID可以通过 docker images 查看
# 格式:registry地址/命名空间/镜像名:版本号
docker tag demo-app:1.0.0 registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:1.0.0
docker tag demo-app:1.0.0 registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:latest
|
第四步:推送镜像
1
2
3
4
5
6
7
8
|
# 推送到阿里云ACR
docker push registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:latest
# 推送过程示例
# docker.io/library/demo-app:1.0.0: Resolving "demo-app"
# ...分层上传...
# 1.0.0: digest: sha256:abc123... size: 1234
|
推送完成后,在阿里云控制台可以看到镜像列表。
4.4 阿里云镜像加速器
如果从Docker Hub拉取官方镜像速度慢,可以配置阿里云镜像加速器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 创建 Docker 配置目录(如果没有)
sudo mkdir -p /etc/docker
# 配置镜像加速器(每个账号的加速地址不同,需在阿里云控制台获取)
sudo tee /etc/docker/daemon.json <<EOF
{
"registry-mirrors": ["https://your-id.mirror.aliyuncs.com"]
}
EOF
# 重启 Docker 服务
sudo systemctl daemon-reload
sudo systemctl restart docker
# 验证加速器配置
docker info | grep "Registry Mirrors"
# Registry Mirrors:
# https://your-id.mirror.aliyuncs.com/
|
4.5 一键构建:从源代码到镜像
ACR支持容器镜像服务ACR EE的ACR Build,直接从源码构建镜像,你甚至不需要在本地安装Docker:
1
2
3
4
5
6
7
|
流程:
Git 仓库(GitHub/GitLab/阿里云Code)→ 触发构建 → ACR Build 自动编译 → 镜像推送到仓库
优势:
- 本地无需安装 Docker
- 云端构建环境标准化
- 自动支持多架构构建(AMD64 + ARM64)
|
配置方法:阿里云控制台 → 容器镜像服务 → 构建 → 绑定代码仓库 → 设置 Dockerfile 路径和构建规则。
五、推送到自建Harbor仓库
5.1 Harbor是什么
Harbor 是 VMware 开源的企业级 Docker Registry 项目,由 CNCF 孵化,是私有镜像仓库的首选方案。
相比直接使用 Docker Registry(开源但功能简单),Harbor 提供了:
1
2
3
4
5
6
7
|
- Web UI(可视化管理界面)
- 镜像安全扫描(Trivy / Clair)
- 镜像签名与内容信任(Notary)
- 角色权限管理(RBAC)
- 镜像复制(跨数据中心同步)
- LDAP/AD 集成(企业统一认证)
- 镜像清理策略(自动删除旧镜像)
|
5.2 安装Harbor
Harbor 支持多种安装方式,生产环境推荐使用 Docker Compose 或 Helm(部署在 Kubernetes 上)。
方式一:Docker Compose 安装(适用于2-10台主机的规模)
1
2
3
4
5
6
7
8
9
10
|
# 下载 Harbor 安装包(选择稳定版本)
wget https://github.com/goharbor/harbor/releases/download/v2.10.2/harbor-online-installer-v2.10.2.tgz
# 解压
tar -xzf harbor-online-installer-v2.10.2.tgz
cd harbor
# 复制并编辑配置文件
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
|
harbor.yml 核心配置项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# hostname:访问 Harbor 的域名或 IP
hostname: harbor.your-domain.com
# http:非 HTTPS 配置(仅供内网测试使用)
# 生产环境务必配置 HTTPS
http:
port: 80
# https:生产环境必须配置
# 需要提前准备好 SSL 证书
https:
port: 443
certificate: /your/cert/path/your-domain.com.crt
private_key: /your/cert/path/your-domain.com.key
# 数据库配置
database:
password: ChangeThisPassword123!
# Admin 初始密码
harbor_admin_password: ChangeThisAdminPassword123!
# 镜像存储路径
image_storage_path: /data/harbor/registry
# 是否有LDAP(企业环境建议开启)
# ldap_url: ldap://ldap.your-domain.com
# ldap_searchdn: cn=admin,dc=your-domain,dc=com
# ldap_searchpw: YourLDAPPassword
# ldap_basedn: dc=your-domain,dc=com
|
1
2
3
4
5
6
7
8
|
# 安装 Harbor(会自动下载所需 Docker 镜像)
sudo ./install.sh
# 安装带 Trivy 安全扫描的版本
sudo ./install.sh --with-trivy
# 安装完整版本(包含所有插件)
sudo ./install.sh --with-notary --with-trivy --with-clair
|
方式二:Helm 安装(适用于 Kubernetes 环境)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 添加 Harbor Helm 仓库
helm repo add harbor https://helm.goharbor.io
helm repo update
# 安装 Harbor 到 Kubernetes
helm install harbor harbor/harbor \
--namespace harbor \
--create-namespace \
--set expose.ingress.hosts.core=harbor.your-domain.com \
--set expose.ingress.hosts.notary=harbor.your-domain.com \
--set expose.tls.enabled=false \
--set persistence.persistentVolumeClaim.registry.size=100Gi \
--set adminPassword=YourSecurePassword123
|
5.3 配置Harbor
创建项目(Project):
Harbor 中的"项目"类似于阿里云ACR的"命名空间",用于隔离不同团队或应用的镜像:
1
2
3
4
|
Harbor 项目类型:
- 私有项目(Private):只有被授权的用户可以拉取/推送
- 公有项目(Public):任何人都可以拉取,但推送仍需授权
- 系统项目(System):用于镜像复制(Replication)
|
通过 Harbor Web UI 创建:
1
2
|
登录地址:http://harbor.your-domain.com(默认 admin / Harbor12345)
→ 新建项目 → 项目名称:demo → 访问级别:私有 → 确定
|
创建用户:
1
2
3
4
|
Harbor Web UI → 系统管理 → 用户管理 → 创建用户
用户名:developer
邮箱:developer@your-domain.com
密码:******
|
分配权限:
1
|
项目 → demo → 成员 → 添加成员 → 选择角色(开发者/维护者/管理员)
|
5.4 推送镜像到Harbor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# 第一步:配置 Docker(添加 Harbor 为可信仓库)
# 如果 Harbor 使用的是自签名证书(测试环境),需要把证书加入 Docker 信任列表
# 方法一:修改 /etc/docker/daemon.json
sudo tee /etc/docker/daemon.json <<EOF
{
"insecure-registries": ["harbor.your-domain.com"],
"registry-mirrors": ["https://your-id.mirror.aliyuncs.com"]
}
EOF
sudo systemctl restart docker
# 方法二(推荐生产使用):导入真实证书
sudo cp your-domain.com.crt /etc/docker/certs.d/harbor.your-domain.com/ca.crt
sudo systemctl restart docker
# 第二步:登录 Harbor
docker login harbor.your-domain.com
# Username: admin
# Password: Harbor12345
# 第三步:打标签
docker tag demo-app:1.0.0 harbor.your-domain.com/demo/demo-app:1.0.0
docker tag demo-app:1.0.0 harbor.your-domain.com/demo/demo-app:latest
# 第四步:推送
docker push harbor.your-domain.com/demo/demo-app:1.0.0
# 第五步:验证
# 访问 http://harbor.your-domain.com → 查看项目 demo → 可以看到镜像列表
|
5.5 Harbor 高可用与灾备
生产环境 Harbor 推荐部署多个副本,通过 PostgreSQL 主从复制 + Redis 共享会话 + 共享存储(NFS/Ceph) 实现高可用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Harbor 高可用架构:
┌─────────────────────────────────────────────────┐
│ 负载均衡器 │
│ (Nginx / F5 / SLB) │
└────────────────────┬───────────────────────────┘
│
┌─────────────┼─────────────┐
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Harbor │ │ Harbor │ │ Harbor │
│ Node 1 │ │ Node 2 │ │ Node 3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
↓
┌──────────────────────────────┐
│ PostgreSQL 主从复制 │
│ Redis 共享会话 │
│ NFS/对象存储 共享镜像存储 │
└──────────────────────────────┘
|
六、生产服务器一键部署
6.1 核心部署命令
无论镜像在阿里云ACR还是自建Harbor,部署命令都是完全一样的。只需要替换镜像地址即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 一行命令:拉取镜像并启动容器
docker run -d \
--name demo-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_HOST=10.0.1.100 \
-e DB_PORT=3306 \
-e DB_NAME=demo \
-e DB_USER=demo_user \
--restart=unless-stopped \
--log-opt max-size=100m \
--log-opt max-file=5 \
registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:1.0.0
# 参数说明:
# -d 后台运行(detached)
# --name demo-app 容器名称
# -p 8080:8080 端口映射(主机端口:容器端口)
# -e 设置环境变量(Spring Boot 读取)
# --restart=unless-stopped 容器退出时自动重启(服务器重启也自动恢复)
# --log-opt 日志轮转:单个日志文件最大100MB,保留5个文件
|
6.2 一键部署脚本
把部署命令写成一个 shell 脚本,方便复用和版本管理:
deploy.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
#!/bin/bash
# ============================================
# 一键部署脚本 - Java Docker 应用
# 用法:./deploy.sh [镜像地址] [版本]
# 示例:./deploy.sh registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app 1.0.0
# ============================================
set -e # 遇到错误立即退出
# 默认配置
IMAGE_REGISTRY="${1:-registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app}"
VERSION="${2:-latest}"
CONTAINER_NAME="demo-app"
HOST_PORT="8080"
CONTAINER_PORT="8080"
# 完整镜像地址
FULL_IMAGE="${IMAGE_REGISTRY}:${VERSION}"
echo "========================================"
echo " Java Docker 一键部署脚本"
echo "========================================"
echo " 镜像地址:${FULL_IMAGE}"
echo " 容器名称:${CONTAINER_NAME}"
echo " 端口映射:${HOST_PORT}:${CONTAINER_PORT}"
echo "========================================"
# 1. 登录镜像仓库(根据镜像地址自动判断)
if [[ "$IMAGE_REGISTRY" == *"aliyuncs.com"* ]]; then
echo "[1/6] 登录阿里云ACR..."
docker login --username="${ALIYUN_USERNAME:-your-namespace}" \
registry.cn-hangzhou.aliyuncs.com || true
elif [[ "$IMAGE_REGISTRY" == *"harbor"* ]]; then
echo "[1/6] 登录 Harbor..."
docker login --username="${HARBOR_USERNAME:-admin}" \
"$(echo $IMAGE_REGISTRY | awk -F/ '{print $1}')" || true
fi
# 2. 拉取最新镜像
echo "[2/6] 拉取镜像 ${FULL_IMAGE}..."
docker pull "${FULL_IMAGE}"
# 3. 停止并删除旧容器(如果存在)
echo "[3/6] 清理旧容器..."
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
# 4. 启动新容器
echo "[4/6] 启动新容器..."
docker run -d \
--name "${CONTAINER_NAME}" \
-p "${HOST_PORT}:${CONTAINER_PORT}" \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_HOST="${DB_HOST:-localhost}" \
-e DB_PORT="${DB_PORT:-3306}" \
-e DB_NAME="${DB_NAME:-demo}" \
-e DB_USER="${DB_USER:-root}" \
-e DB_PASSWORD="${DB_PASSWORD}" \
--restart=unless-stopped \
--log-opt max-size=100m \
--log-opt max-file=5 \
--memory=512m \
--memory-reservation=256m \
"${FULL_IMAGE}"
# 5. 检查容器健康状态
echo "[5/6] 检查容器启动状态..."
sleep 5
if docker ps | grep -q "${CONTAINER_NAME}"; then
echo " ✓ 容器启动成功"
else
echo " ✗ 容器启动失败,查看日志:"
docker logs "${CONTAINER_NAME}"
exit 1
fi
# 6. 显示部署结果
echo "[6/6] 部署完成!"
echo ""
echo " 应用地址:http://localhost:${HOST_PORT}/api/hello"
echo " 健康检查:http://localhost:${HOST_PORT}/api/health"
echo ""
echo " 常用命令:"
echo " - 查看日志:docker logs -f ${CONTAINER_NAME}"
echo " - 进入容器:docker exec -it ${CONTAINER_NAME} /bin/sh"
echo " - 停止应用:docker stop ${CONTAINER_NAME}"
echo " - 重启应用:docker restart ${CONTAINER_NAME}"
echo "========================================"
|
1
2
3
4
5
6
7
8
9
10
11
|
# 给脚本加执行权限
chmod +x deploy.sh
# 部署到阿里云ACR
./deploy.sh registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app 1.0.0
# 部署到自建Harbor
./deploy.sh harbor.your-domain.com/demo/demo-app 1.0.0
# 部署最新版本
./deploy.sh registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app latest
|
6.3 生产环境一键部署(Systemd 管理)
在生产服务器上,用 Systemd 管理容器比直接 docker run 更安全——Systemd 会在服务器重启后自动拉起容器,并记录日志到系统日志:
/etc/systemd/system/demo-app.service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
[Unit]
Description=Java Demo Application
Documentation=https://github.com/your-org/demo-app
After=network-online.target
Wants=network-online.target docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
# 环境变量文件(敏感信息放这里,不写在 service 文件里)
EnvironmentFile=/etc/demo-app/env.conf
ExecStartPre=-/usr/bin/docker stop demo-app
ExecStartPre=-/usr/bin/docker rm demo-app
ExecStart=/usr/bin/docker run \
--name demo-app \
-p 8080:8080 \
--env-file /etc/demo-app/env.conf \
--restart=unless-stopped \
--log-opt max-size=100m \
--log-opt max-file=5 \
--memory=512m \
--memory-reservation=256m \
--cpus=1.0 \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
"${IMAGE_REGISTRY}:${IMAGE_TAG}"
ExecStop=/usr/bin/docker stop -t 30 demo-app
ExecStop=/usr/bin/docker rm demo-app
# 安全设置
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
|
/etc/demo-app/env.conf(环境变量,存放数据库密码等敏感信息):
1
2
3
4
5
6
|
SPRING_PROFILES_ACTIVE=prod
DB_HOST=10.0.1.100
DB_PORT=3306
DB_NAME=demo
DB_USER=demo_user
DB_PASSWORD=YourSecureDBPassword123!
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# 部署步骤
sudo cp demo-app.service /etc/systemd/system/
sudo cp env.conf /etc/demo-app/env.conf
sudo chmod 600 /etc/demo-app/env.conf # 限制权限
# 设置镜像地址和版本(可以用脚本自动修改)
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start demo-app
# 设置开机自启
sudo systemctl enable demo-app
# 查看服务状态
sudo systemctl status demo-app
# 查看日志
sudo journalctl -u demo-app -f
|
6.4 更新版本:一条命令搞定
当有新版本需要发布时,只需要改一行配置,然后重启服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 编辑版本配置
sudo vim /etc/demo-app/env.conf
# 修改 IMAGE_TAG=1.0.1
# 重启服务(自动拉取新镜像、重建容器)
sudo systemctl restart demo-app
# 查看是否升级成功
curl http://localhost:8080/api/hello
# 应该返回 "Hello from Docker! Version: 1.0.1"
# 查看升级日志
sudo journalctl -u demo-app -n 20 --no-pager
|
七、数据持久化与配置管理
7.1 数据持久化:数据不能丢
Java应用如果有本地文件存储(如上传文件、日志),必须配置 Docker 数据卷,否则容器删除后数据就没了:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 创建数据卷
docker volume create demo-app-data
# 启动时挂载数据卷
docker run -d \
--name demo-app \
-v demo-app-data:/app/data \
-p 8080:8080 \
registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:1.0.0
# 查看数据卷信息
docker inspect demo-app --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
|
生产环境推荐:使用云存储或分布式存储(如阿里云 OSS、MinIO)存储应用数据,而不是本地数据卷。
7.2 使用配置中心
生产环境的配置不应该写死在镜像里。推荐使用 Spring Cloud Config 或 Nacos 配置中心:
1
2
3
4
5
6
7
8
9
10
11
|
# application.yml 中配置 Nacos
spring:
cloud:
nacos:
config:
server-addr: nacos-service:8848
namespace: production
group: DEFAULT_GROUP
file-extension: yaml
discovery:
server-addr: nacos-service:8848
|
配置中心的好处:改配置不需要重新打包镜像,不需要重启容器,配置变更自动推送到应用。
7.3 敏感信息管理
不要把密码、密钥写在 Dockerfile 或 env.conf 中:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 错误做法(密码写在代码里)
ENV DB_PASSWORD=MySecretPassword123
# 正确做法一:运行时通过环境变量传入
docker run -e DB_PASSWORD="$(cat /run/secrets/db_password)" ...
# 正确做法二:使用 Docker Secrets(Swarm 模式)
echo "MySecretPassword123" | docker secret create db_password -
docker service create --secret db_password ...
# 正确做法三:使用 Vault(HashiCorp Vault)管理密钥
# 应用从 Vault API 获取密钥,不在环境中明文存储
|
八、最佳实践与常见问题
8.1 镜像优化的黄金法则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
镜像体积:越小越好
✓ 多阶段构建(只保留运行时文件)
✓ 使用 alpine / distroless 等精简基础镜像
✓ 利用 .dockerignore 排除无关文件
✓ Java 17+ 推荐使用 jlink 自定义 JRE(只包含需要的模块)
构建速度:越快越好
✓ .dockerignore 减少 COPY 范围
✓ COPY 指令顺序:先复制 pom.xml,再复制源码
✓ 利用 Docker 层缓存(不变的内容放前面)
安全性:越严越好
✓ 非 root 用户运行容器
✓ 只读根文件系统(--read-only)
✓ 丢弃所有 Linux 能力(--cap-drop ALL)
✓ 不使用 latest 标签(每次指定明确版本)
✓ 定期扫描镜像漏洞(Trivy / Clair)
|
8.2 Java应用容器化的特殊注意事项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# JVM 内存设置:容器有内存限制时,JVM 需要感知到
# Java 10+ 自动检测容器内存限制(推荐)
docker run -m 512m ... demo-app:1.0.0
# JVM 会自动把 -Xmx 设置为容器内存的 25-50%
# Java 8/9 需要手动指定
docker run -m 512m \
-e JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" \
demo-app:1.0.0
# 禁止 JVM 自动检测(某些场景需要)
-e JAVA_OPTS="-XX:-UseContainerSupport"
# GC 优化:容器化环境推荐使用 G1 GC
-e JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# 时区问题:Java 默认用 UTC,生产环境需要设置时区
-e TZ=Asia/Shanghai
# 或在 Dockerfile 中:
RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
8.3 常见报错与解决
错误一:镜像拉取失败(image not found)
1
2
3
4
|
# 错误信息:Error response from daemon: pull access denied
# 解决:检查镜像地址和登录凭证
docker images | grep demo-app
docker login <registry-url>
|
错误二:端口被占用(port already allocated)
1
2
3
4
5
|
# 错误信息:Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use
# 解决:查看占用端口的进程
lsof -i:8080
# 或换一个端口
docker run -p 8081:8080 ...
|
错误三:容器启动后立即退出(exited with code 0)
1
2
3
4
5
6
7
|
# 查看容器日志,找具体原因
docker logs demo-app
# 常见原因:
# - Java 应用启动失败(端口冲突、数据库连不上)
# - 健康检查失败(HEALTHCHECK 指令)
# - 权限问题(挂载的目录没有写权限)
|
错误四:内存溢出(OOMKilled)
1
2
3
4
5
6
7
|
# 查看容器状态
docker stats --no-stream demo-app
# 或
docker inspect demo-app | grep -A 5 OOMKilled
# 解决:增加内存限制或优化 JVM 堆大小
docker run -m 1g --memory-swap=1g ...
|
8.4 从Docker到Kubernetes
当应用数量增加,需要多个容器协调工作时,单台服务器的 Docker 就力不从心了。Kubernetes(K8s) 是容器编排的事实标准:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
Docker(单主机管理):
docker run -d demo-app:1.0.0
只能管理一台服务器上的容器
Kubernetes(多主机编排):
kubectl apply -f deployment.yaml
自动管理跨多台服务器的数百个容器副本
一个 deployment.yaml 示例(Kubernetes 部署描述文件):
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: registry.cn-hangzhou.aliyuncs.com/your-namespace/demo-app:1.0.0
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
---
apiVersion: v1
kind: Service
metadata:
name: demo-app
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: demo-app
|
如果你已经有了打包好的 Docker 镜像,迁移到 Kubernetes 几乎不需要修改任何代码——Docker 镜像是 Kubernetes 的通用交付格式。
结语
回顾整个流程,我们完成了一条完整链路:
1
2
3
4
5
6
|
代码 → Maven 打包 → Docker 多阶段构建 → 推送仓库 → 一键部署
本地开发(Docker Desktop)
→ 阿里云 ACR(云端托管)
→ 自建 Harbor(私有可控)
→ 生产服务器(Systemd 管理)
|
Docker 解决的不仅是"在我机器上能跑"的问题,它让应用的交付从’给源代码让人部署’变成了’给一个镜像让人运行’"。镜像在哪里构建、推到哪里、部署到哪台机器——这些都可以灵活选择,唯一不变的是:一次构建,到处运行。
记住这条黄金法则:
1
2
3
4
|
镜像要小 → 多阶段构建,alpine 基础镜像
构建要快 → .dockerignore + 层缓存
运行要稳 → 非 root 用户 + 资源限制 + 健康检查
配置要灵 → 环境变量 + 配置中心
|
祝你的Java应用容器化之路顺利!
相关推荐阅读: