Docker + Jenkins 自动化部署实战:一行命令,告别凌晨上线
凌晨 2 点,你盯着终端,手敲 `scp` 传包、`ssh` 连服务器、`kill` 旧进程、`nohup` 启动新服务——这是多少程序员的上线噩梦?本文手把手带你搭建 **Docker + Jenkins 全自动 CI/CD 流水线**,从代码提交到服务上线,全程零人工干预。看完即可落地,代码可直接复制运行。
---
一、为什么你需要这套方案?
1.1 传统部署的 4 大痛点
痛点 | 场景 | 后果 |
**人工操作多** | 编译 → 打包 → 传包 → 停服务 → 启服务 | 步骤越多,出错概率越高 |
**环境不一致** | 本地能跑,测试环境报错,生产又不一样 | "我本地明明是好的" |
**回滚困难** | 上线后发现 Bug,找不到上一版本的包 | 凌晨 3 点紧急救火 |
**无法追溯** | 不知道谁、什么时候、部署了哪个版本 | 出问题互相甩锅 |
1.2 CI/CD 能解决什么?![]()
开发者 push 代码
↓
Git 触发 Webhook
↓
Jenkins 自动拉取 → 编译 → 测试 → 打包 Docker 镜像
↓
推送镜像到 Harbor/阿里云仓库
↓
SSH 到目标服务器 → 拉取新镜像 → 停止旧容器 → 启动新容器
↓
发送企微/钉钉通知:部署成功 ✅
全程耗时:3~5 分钟。全程人工操作:0。
---
二、环境准备
2.1 所需机器与软件
角色 | 配置建议 | 安装软件 |
Jenkins 主节点 | 2C4G 起步 | JDK 17、Jenkins LTS、Docker、Git、Maven |
应用服务器(可多台) | 根据业务 | Docker、Docker Compose |
镜像仓库 | 可复用 Jenkins 机器 | Harbor 或阿里云 ACR |
本文演示使用 **单台 Jenkins 机器 + 单台应用服务器**,生产环境按需扩展。
2.2 快速安装 Jenkins(Docker 方式)![]()
# 1. 创建 Jenkins 数据卷
docker volume create jenkins_home
# 2. 启动 Jenkins(已预装 Maven、Docker CLI)
docker run -d \
--name jenkins \
-p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(which docker):/usr/bin/docker \
jenkins/jenkins:lts-jdk17
# 3. 查看初始密码
docker logs jenkins 2>&1 | grep -A 5 "Please use the following password"
访问 `http://<服务器IP>:8080`,输入密码,安装推荐插件。
2.3 Jenkins 必要插件安装
进入Manage Jenkins → Plugins → Available plugins,搜索并安装:
-Pipeline— 流水线核心
-Git— 拉取代码
-Maven Integration— Maven 构建
-SSH Agent/Publish Over SSH— 远程部署
-DingTalk/QyWechat Notification— 企微/钉钉通知(可选)
---
三、核心配置:Jenkins + Docker 打通
3.1 配置 Git 凭据
Manage Jenkins → Credentials → System → Global credentials → Add Credentials
- Kind: `Username with password`
- Username: 你的 Git 用户名
- Password: Git 密码或 Personal Access Token
- ID: 填 `git-creds`(Pipeline 里会引用)
3.2 配置远程服务器 SSH 凭据
同样的位置,再添加一组:
- Kind: `SSH Username with private key`
- Username: `root`(或你的部署账号)
- Private Key: 粘贴你的私钥(`~/.ssh/id_rsa` 内容)
- ID: 填 `deploy-server-ssh`
3.3 配置 Publish Over SSH(远程部署用)
Manage Jenkins → System → Publish over SSH → Add
- Name: `prod-server`
- Hostname: `192.168.1.100`(你的应用服务器 IP)
- Username: `root`
- Remote Directory: `/opt/deploy`
点击Test Configuration,显示 `Success` 即可。
---
四、项目实战:Spring Boot + Docker 全自动部署
4.1 项目结构
my-springboot-app/
├── src/
├── pom.xml
├── Dockerfile
├── docker-compose.yml
└── Jenkinsfile
4.2 Dockerfile(多阶段构建,镜像体积减少 60%)
# 阶段一:编译
FROM maven:3.9-eclipse-temurin-17-alpine AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# 阶段二:运行(仅保留 JRE + jar 包)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
**多阶段构建** 是 Docker 最佳实践:编译阶段用完整 JDK,运行阶段只用 JRE,最终镜像从 500MB 压缩到 80MB。
4.3 docker-compose.yml(生产级配置)
version: '3.8'
services:
app:
image: harbor.mycompany.com/springboot/app:${IMAGE_TAG}
container_name: springboot-app
restart: unless-stopped
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- JAVA_OPTS=-Xms512m -Xmx512m -XX:+UseG1GC
networks:
- app-net
# 日志限制,防止磁盘打满
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
# Nginx 反向代理(可选)
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- app-net
networks:
app-net:
driver: bridge
4.4 Jenkinsfile(Pipeline as Code,核心中的核心)
在项目根目录创建 `Jenkinsfile`:
pipeline {
agent any
environment {
// 镜像仓库配置
REGISTRY = 'harbor.mycompany.com'
REGISTRY_CREDENTIALS = 'harbor-credentials'
IMAGE_NAME = 'springboot/app'
// SSH 目标服务器
DEPLOY_HOST = '192.168.1.100'
DEPLOY_PATH = '/opt/deploy/myapp'
// 企业微信 Webhook(可选)
WECHAT_WEBHOOK = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY'
}
stages {
// ========== 阶段 1:拉取代码 ==========
stage('Checkout') {
steps {
git branch: 'main',
credentialsId: 'git-creds',
url: 'https://github.com/yourname/my-springboot-app.git'
script {
env.GIT_SHORT_SHA = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
env.IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_SHORT_SHA}"
}
}
}
// ========== 阶段 2:Maven 编译 & 测试 ==========
stage('Build & Test') {
steps {
sh 'mvn clean package -DskipTests=false'
// 单元测试报告
junit 'target/surefire-reports/*.xml'
}
}
// ========== 阶段 3:构建 Docker 镜像 ==========
stage('Build Docker Image') {
steps {
script {
docker.withRegistry("https://${REGISTRY}", REGISTRY_CREDENTIALS) {
def customImage = docker.build("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
customImage.push()
// 同时推送 latest 标签,方便快速回滚
customImage.push('latest')
}
}
}
}
// ========== 阶段 4:远程部署 ==========
stage('Deploy') {
steps {
sshagent(credentials: ['deploy-server-ssh']) {
sh """
ssh -o StrictHostKeyChecking=no root@${DEPLOY_HOST} '
set -e
mkdir -p ${DEPLOY_PATH}
cd ${DEPLOY_PATH}
# 备份当前 docker-compose.yml(用于回滚)
if [ -f docker-compose.yml ]; then
cp docker-compose.yml docker-compose.yml.bak
fi
# 写入新的 compose 文件
cat > docker-compose.yml << "EOF"
version: "3.8"
services:
app:
image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
container_name: springboot-app
restart: unless-stopped
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
EOF
# 拉取新镜像并启动
docker-compose pull app
docker-compose up -d app
# 等待服务健康检查通过
echo "等待健康检查..."
for i in {1..30}; do
if docker inspect --format='{{.State.Health.Status}}' springboot-app 2>/dev/null | grep -q "healthy"; then
echo "✅ 服务已就绪"
exit 0
fi
sleep 2
done
echo "❌ 健康检查超时"
exit 1
'
"""
}
}
}
}
// ========== 后置处理:通知 & 清理 ==========
post {
success {
script {
// 企微通知
sh """
curl -s -X POST ${WECHAT_WEBHOOK} \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"content": "🎉 **部署成功**\\n\\n> 项目:my-springboot-app\\n> 版本:${IMAGE_TAG}\\n> 时间:${env.BUILD_TIME}\\n> 提交:${env.GIT_SHORT_SHA}"
}
}'
"""
}
}
failure {
script {
sh """
curl -s -X POST ${WECHAT_WEBHOOK} \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"content": "🚨 **部署失败**\\n\\n> 项目:my-springboot-app\\n> 构建号:${env.BUILD_NUMBER}\\n> 请及时检查 Jenkins 日志"
}
}'
"""
}
}
always {
// 清理本地旧镜像,防止磁盘膨胀
sh 'docker image prune -f || true'
}
}
}
4.5 代码解读:Pipeline 的 5 个阶段
阶段 | 做什么 | 关键技巧 |
**Checkout** | 从 Git 拉最新代码 | 提取短 SHA 作为镜像标签,保证版本可追溯 |
**Build & Test** | Maven 编译 + 单元测试 | 测试失败立即阻断,不部署有问题的代码 |
**Build Image** | Docker 多阶段构建 + 推送仓库 | 同时打 `latest` 标签,方便紧急回滚 |
**Deploy** | SSH 到服务器,拉镜像、启容器 | 内置健康检查,超时自动判定失败 |
**Notify** | 企微/钉钉推送结果 | success/failure/always 三种后置钩子 |
---
五、进阶技巧
5.1 一键回滚(关键时刻救命)![]()
在 Jenkins 中创建一个参数化流水线`rollback`:
pipeline {
agent any
parameters {
string(name: 'ROLLBACK_TAG', defaultValue: '', description: '回滚到的镜像标签')
}
stages {
stage('Rollback') {
steps {
sshagent(credentials: ['deploy-server-ssh']) {
sh """
ssh root@${DEPLOY_HOST} '
cd /opt/deploy/myapp
sed -i "s|image: .*|image: ${REGISTRY}/${IMAGE_NAME}:${ROLLBACK_TAG}|" docker-compose.yml
docker-compose up -d app
'
"""
}
}
}
}
}
回滚只需 30 秒:找到上一次成功的构建号,输入镜像标签,点击构建。
5.2 蓝绿部署(零停机发布)
# docker-compose.blue-green.yml
services:
app-blue:
image: ${IMAGE_TAG}
container_name: app-blue
ports: ["8081:8080"]
app-green:
image: ${IMAGE_TAG}
container_name: app-green
ports: ["8082:8080"]
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
流程:
1. 绿环境部署新版本
2. Nginx 切流量到绿环境
3. 观察 5 分钟无异常
4. 蓝环境更新,作为下次发布的基础
5.3 Jenkins 与 Git Webhook 联动(自动触发)
GitHub 设置:
1. 进入仓库 → Settings → Webhooks → Add webhook
2. Payload URL: `http://<JenkinsIP>:8080/github-webhook/`
3. Content type: `application/json`
4. 勾选Just the push event
Jenkins 项目设置:
1. 构建触发器 → 勾选GitHub hook trigger for GITScm polling
效果:每次 `git push` 到 `main` 分支,Jenkins 自动开始构建,全程无需人工干预。
---
六、常见问题排查
Q1:Jenkins 构建报错 "Cannot connect to the Docker daemon"
原因:Jenkins 容器内没有 Docker 权限。
解决:启动 Jenkins 时挂载 Docker socket:
-v /var/run/docker.sock:/var/run/docker.sock
-v $(which docker):/usr/bin/docker
Q2:SSH 连接失败 "Host key verification failed"
解决:在 Pipeline 的 ssh 命令中加 `-o StrictHostKeyChecking=no`,或在 Jenkins 服务器手动 `ssh` 一次目标机器。
Q3:镜像推送 Harbor 报 401 Unauthorized
解决:Jenkins 凭据里添加 Harbor 账号密码,ID 与 `Jenkinsfile` 中的 `REGISTRY_CREDENTIALS` 一致。
Q4:健康检查一直不通过
排查步骤:
1. `docker logs springboot-app` 看应用日志
2. 确认 `actuator/health` 端点已开启(Spring Boot 需引入 `spring-boot-starter-actuator`)
3. 检查防火墙是否放行 8080 端口
---
七、完整架构图
┌─────────────────────────────────────────────────────────────┐
│ 开发者 │
│ git push origin main │
└──────────────────────────┬──────────────────────────────────┘
│ Webhook
▼
┌─────────────────────────────────────────────────────────────┐
│ Jenkins 主节点 │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Checkout│→ │Build&Test│→ │Build Img │→ │ Deploy │ │
│ │ 拉代码 │ │ Maven编译 │ │Docker构建 │ │ SSH远程部署 │ │
│ └─────────┘ └──────────┘ └──────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Harbor 镜像仓库 │ │
│ │ (版本化存储镜像) │ │
│ └─────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│ docker pull
▼
┌─────────────────────────────────────────────────────────────┐
│ 生产服务器集群 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Docker容器 │ │ Docker容器 │ │ Docker容器 │ │
│ │ SpringBoot │ │ SpringBoot │ │ SpringBoot │ │
│ │ App v1.2 │ │ App v1.2 │ │ App v1.2 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ↑ │
│ 健康检查通过 → 企微通知 "部署成功" │
└─────────────────────────────────────────────────────────────┘
---
八、总结与生产 Checklist
核心收益
指标 | 传统部署 | CI/CD 自动化 |
部署耗时 | 15~30 分钟 | 3~5 分钟 |
人工操作 | 5~10 步 | 0 步 |
回滚时间 | 10 分钟以上 | 30 秒 |
环境一致性 | 差 | 100% 一致 |
版本可追溯 | 无 | 每次构建有唯一标签 |
生产环境 Checklist
- [ ] Jenkins 开启权限控制(Matrix-based security)
- [ ] 镜像仓库启用 HTTPS + 账号认证
- [ ] 部署脚本中加入 `set -e`,任何步骤失败立即退出
- [ ] 配置日志收集(ELK / Loki)
- [ ] 监控告警:Prometheus + Grafana 监控容器状态
- [ ] 定期清理旧镜像,防止磁盘打满
- [ ] 敏感信息(密码、Token)存 Jenkins 凭据,不要写死在代码里
---
如果这篇文章对你有帮助,欢迎点赞、收藏、转发!关注公众号「洛水石」,获取更多后端实战干货。
