元数据卡
- 前置知识:终端(ch02)、Git(ch03)、构建工具(ch05)、Docker(ch07)
- 预计时间:60 分钟
- 核心难度:(黑魔法)
- 阅读模式: 高度专注
- 完成标志:能独立走通"写代码→编译→打包→容器化→部署上线"的全流程
你的进度
装备齐全了。终端是你的武器,Git 是时光机,调试器是显微镜,构建工具是补给站——上一章你甚至学会了把整间工坊打包成 Docker 箱子。
你还站在工坊里,但门已经开了一条缝。工坊的师傅这次没有给你新工具——他指了指门外。
现在该上路了。
前七章我们一直在工坊里练刀。现在工坊的门打开了:你的第一行代码,要真正变成用户能访问的东西。
你的任务
本章分层
- 必读:理解代码从源码到可运行程序的大致链路(写代码 → 编译打包 → 容器化),能在本地完成这个最小闭环
- 选读:将镜像推送到远程仓库(GHCR/Docker Hub)、用 CI/CD 工具(GitHub Actions)自动化构建、部署到云服务器
- 进阶:反向代理配置(Nginx/Caddy)、蓝绿部署与灰度发布、Kubernetes 部署
本章不会要求你掌握
- GitHub Actions 的完整语法——能看懂 workflow 文件就够了
- Spring Boot 的内部原理——它只是用来演示"能跑的东西"
- 生产环境的服务器配置(反向代理、HTTPS、监控)——等你真有用户上线再学
从零开始创建一个真实项目——写代码、构建、测试、容器化——跑完本地闭环,看到你的代码从小小的编辑器变成一个能跑的服务。
读完这章,你已经知道了代码从编辑器到被用户访问的全部环节。但部署到公网是项目实战阶段的事,本章不把它作为通关条件。
破局 · 溯源
起手:一个最简单的 Web 应用
我先声明一件事:这一章的目的是用最短路径跑通全链路,不是把每个环节都拆到基因层面。效率胜过完美。
你已经会 Maven 了(第 5 章),现在用它创建一个最简的 Spring Boot 项目。不用 IDE 模板,命令行搞定——终端才是真正的战场。
# 创建一个 Maven 项目目录结构
mkdir -p hello-atlas/src/main/java/com/example
mkdir -p hello-atlas/src/main/resources
cd hello-atlas在项目根目录创建 pom.xml:
<?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>
</parent>
<groupId>com.example</groupId>
<artifactId>hello-atlas</artifactId>
<version>1.0.0</version>
<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>主应用类 src/main/java/com/example/HelloAtlasApplication.java:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class HelloAtlasApplication {
public static void main(String[] args) {
SpringApplication.run(HelloAtlasApplication.class args);
}
@GetMapping("/")
public String hello() {
return "Hello Atlas! 出发!";
}
}配置文件 src/main/resources/application.properties:
server.port=8080目录结构应该长这样:
hello-atlas/
├── pom.xml
└── src/
└── main/
├── java/
│ └── com/
│ └── example/
│ └── HelloAtlasApplication.java
└── resources/
└── application.properties现在编译并运行:
mvn clean package -DskipTests
java -jar target/hello-atlas-1.0.0.jar打开浏览器,输入 http://localhost:8080。
看到 "Hello Atlas! 出发!" 了吗?
你刚走过了前两站:写代码 → 编译打包。 那个 target/hello-atlas-1.0.0.jar 是一个 fat JAR——里面打包了所有依赖和嵌入式的 Tomcat 服务器。一个文件,就能跑。
容器化:把 JAR 装进箱子
跑在本机上只是第一步。现在用上一件的技能打包它。
在项目根目录创建 Dockerfile:
FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/hello-atlas-1.0.0.jar app.jar
EXPOSE 8080
CMD ["java" "-jar" "app.jar"]构建镜像:
docker build -t hello-atlas:v1 .启动容器:
docker run -d -p 8080:8080 --name hello-atlas hello-atlas:v1访问 http://localhost:8080,看到一样的欢迎页面。
两站走完了。现在要把这口箱子送到服务器上。
项目实战:将镜像推送到远程仓库和 CI/CD 自动化 以下内容不是零基础通关条件。等你有了自己的 GitHub 项目、想在云上部署时再回来看。
推送到仓库:把箱子搬到"公共驿站"
你的箱子现在只有本地有。要部署到一台远程服务器,你需要一个"公共驿站"——镜像仓库。
主流选择是 Docker Hub 或 GitHub Container Registry(GHCR)。这里用 GHCR,因为它跟你的 GitHub 账号打通,不需要额外注册。
第一步:登录
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdinGITHUB_TOKEN 是你之前在 GitHub Settings → Developer Settings → Personal access tokens 里创建的,权限需要 write:packages。
第二步:给镜像打上仓库标签
docker tag hello-atlas:v1 ghcr.io/YOUR_GITHUB_USERNAME/hello-atlas:v1第三步:推送到远程仓库
docker push ghcr.io/YOUR_GITHUB_USERNAME/hello-atlas:v1推送成功后,任何人都能从仓库拉取你的镜像——只要有权限。
CI/CD:让机器替你走路
你手动推送了一次,成功了。但第二天你又改了一行代码——然后重复了六步操作:git push、mvn clean package、docker build、docker push、ssh server、docker pull、docker restart……
第三天,你忘了一步。
线上挂了。用户看不到更新了。你手忙脚乱地翻终端历史,找上次是怎么部署的。
"我能不能写一个脚本,让我推完代码它就全自动走完?"你问。"我不想再手敲这七条命令了——我总会漏掉一条的。"
项目实战: 以下自动化流程等你开始频繁部署时再设置。现在先看懂概念,需要时回来抄配置。
手动构建→推送实在太原始了。每次改一行代码你都要重复:
git push
mvn clean package
docker build
docker push
ssh server
docker pull
docker restart太烦了。而且你一定会忘记某一步。
CI/CD 就是自动化这个流程——你把代码推到 GitHub,GitHub Actions 自动帮你编译、测试、打包、推送、部署。
在项目根目录创建 .github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package -DskipTests
- name: Build Docker image
run: docker build -t hello-atlas:${{ github.sha }} .
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
docker tag hello-atlas:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
docker push ghcr.io/${{ github.repository }}:latest每次你 git push 到 main 分支,GitHub 就会帮你跑完上面所有的步骤。你可以去 GitHub 仓库页面的 Actions 标签页,看到每次构建的实时日志:
Step 1/6: Set up JDK 17 → 配置 JDK
Step 2/6: Build with Maven → 编译打包
Step 3/6: Build Docker image → 构建镜像
Step 4/6: Log in to GHCR → 登录仓库
Step 5/6: Push image → 推送镜像在浏览器里看你的代码从编译到推送的全过程——比手动敲几十行命令爽多了。
部署到服务器:让箱子在远方落地
项目实战: 将代码部署到公网服务器是最终目标,但那是你有一个真实项目之后的事。本章先看懂流程,等你有服务器了再回来动手。
镜像到了仓库,接下来需要一台能运行它的服务器。你可以用一台便宜的云服务器(VPS),或者更轻量的平台比如 Railway、Render、Fly.io。
这里演示最通用的模式——SSH 部署到自己的服务器。
先在自己服务器上装好 Docker,然后登录:
# 登录到服务器
ssh user@your-server-ip
# 拉取镜像
docker pull ghcr.io/YOUR_GITHUB_USERNAME/hello-atlas:latest
# 运行容器
docker run -d -p 80:8080 --restart unless-stopped \
--name hello-atlas \
ghcr.io/YOUR_GITHUB_USERNAME/hello-atlas:latest注意:宿主机端口我改成了 80:8080——把容器内的 8080 端口映射到宿主机标准的 HTTP 端口 80。
现在,在浏览器里输入 http://your-server-ip。
如果你的服务器在公网上,全世界的人都能看到这句"Hello Atlas! 出发!"了。
这就是部署的本质:让你的代码在另一台机器上跑起来,接受外部访问。
常见陷阱
案例 1:"端口冲突"
你在本地调试时,java -jar 占着 8080,docker run 也占着 8080——报错 "Address already in use"。
# 找到谁占着 8080
sudo lsof -i :8080
# 结果是之前启动的 Java 进程
# 方案 1:杀掉它
kill -9 <PID>
# 方案 2:换个端口启动 Docker
docker run -d -p 8081:8080 hello-atlas:v1上线时容易犯的反向错误:你把容器端口映射到宿主机 8080,但你的 Nginx 反向代理配置的是 80 端口,死活连不上——最后发现是端口映射写错了。
案例 2:"GitHub Actions 构建失败,但本地能跑"
本地 mvn clean package 成功了,推到 GitHub 却失败。日志显示:GitHub Actions 的 Ubuntu runner 没有 Docker 或 JDK 版本不对。
问题可能出在 actions/setup-java 版本或 runner 的默认环境。去 GitHub 的 Actions 标签页看构建日志,找到失败步骤。常见的坑:GITHUB_TOKEN 权限不足(需要在 repo Settings → Actions → General 里启用读写权限)。
案例 3:"我改了代码,但线上还是旧的"
最让人崩溃的事——你推了代码推送了镜像重启了容器,但用户看到的还是旧页面。
排查步骤:
# 1. 确认容器运行的是最新的镜像
docker inspect hello-atlas | grep Image
# 2. 确认最新镜像的构建时间符合预期
docker images | grep hello-atlas
# 3. 如果镜像没错,看看是不是浏览器缓存了旧页面
curl -I http://localhost:8080
# 看 Last-Modified 头或者打开无痕窗口最常见的真实原因:你忘记重新拉取 latest 标签。Docker 的 :latest 标签是动态的,你服务器上的镜像还是旧版本。解决方案:部署脚本里先 docker pull 再启动,并且给镜像带上明确的版本号(如 v1.0.1),不要只依赖 latest。
通关挑战
热身:从头创建本章的 hello-atlas 项目,在本地跑起来,访问
localhost:8080看到欢迎页面。挑战:编写 Dockerfile 将你的 hello-atlas 应用容器化,用
docker build和docker run在本地启动容器,确保能通过localhost:8080访问。项目实战(选做):将应用推送到 GitHub Container Registry,然后在云服务器中部署它。这是你有了真实项目后的下一个大目标,但不是本章通关要求。
观察:
- 在容器内执行
curl localhost:8080观察返回内容。 - 查看
docker logs的输出,看看哪些是 Spring Boot 的启动日志,哪些是应用日志。 - 访问
curl -v localhost:8080观察 HTTP 请求-响应的完整过程。
- 排障: 你在服务器上执行
docker run -d -p 80:8080 hello-atlas:v1,但是docker ps看不到容器,docker ps -a显示容器已退出。查看docker logs <容器ID>,发现 "no main manifest attribute in /app/app.jar"。这是什么意思?怎么解决?
提示:检查你的 Dockerfile 里的 CMD 命令是否正确,以及 JAR 包构建时是否配置了正确的入口类。
验收标准
- 能说出代码从编辑器到可运行程序的完整链路:写代码 → 编译打包 → 容器化 → 推送仓库 → 部署运行
- 能在不用 IDE 的情况下从零创建并编译一个最小的 Spring Boot 项目
- 能用 Dockerfile 容器化应用,并在本地运行它
- 能解释 CI/CD 的基本概念——"自动化构建、测试、部署"
- 知道镜像仓库的作用——让"推上去,拉下来"取代"传文件的游戏"
- 能诊断本地端口冲突的问题
常见卡点
- **"mvn: command not found":Maven 没装或没配置环境变量。检查
echo $JAVA_HOME和mvn --version。如果装了但找不到,确认M2_HOME环境变量。 - "docker login 失败":检查 GITHUB_TOKEN 是否过期,以及 token 是否有
write:packages权限。GitHub 的 token 有效期默认 30 天,超期了要重新生成。 - "GitHub Actions workflow 没有触发":检查 workflow 文件是否在
.github/workflows/目录下,以及分支名是否与on.push.branches一致。文件名大小写敏感。 - "服务器 SSH 连不上":检查防火墙是否放行了 22 端口。大多数云服务商默认只放行 80/443。另外确认安全组规则是否允许你的 IP 连接。
- "curl 访问服务器 IP 没反应":检查服务器防火墙是否放行了 80 端口。另外容器是否真的在运行(
docker ps看状态)。
现在不需要理解
- Spring Boot 的原理:这一章只是用它来展示"能跑的东西"。它的内部机制(自动配置、IoC 容器、内嵌 Tomcat)是 Vol 6 软件工程的内容。
- 反向代理(Nginx/Caddy)的配置:生产环境通常会在应用前面放一个反向代理来处理 TLS 终止、静态资源、限流。暂时用直连 8080 就够了。
- Kubernetes 部署:单个容器跑在单台服务器很简单。当你有几十个服务、几百台机器时需要 K8s。那是 Vol 7 的事。
- 蓝绿部署、灰度发布:你现在的用户只有你自己。等有真用户了再考虑这些。
旅人笔记
从编辑器里的一行代码,到公网上任意一个浏览器能访问的服务,你走完了六个阶段:写代码 → 编译打包 → 容器化 → 推送仓库 → CI/CD 自动化 → 部署上线。每多走一个阶段,你的代码就离"真正被别人用"近了一步。现在你知道了——部署不是魔法,是一条可以自动化走通的路。
→ 下一站预告
工坊的师傅拍了拍你的肩:"出门朝东走,有个叫变量村的地方,那边有位老陈师傅——他能教你真本事。"