Skip to content

元数据卡

  • 前置知识:终端(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 模板,命令行搞定——终端才是真正的战场。

bash
# 创建一个 Maven 项目目录结构
mkdir -p hello-atlas/src/main/java/com/example
mkdir -p hello-atlas/src/main/resources
cd hello-atlas

在项目根目录创建 pom.xml

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

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

properties
server.port=8080

目录结构应该长这样:

hello-atlas/
├── pom.xml
└── src/
 └── main/
 ├── java/
 │ └── com/
 │ └── example/
 │ └── HelloAtlasApplication.java
 └── resources/
 └── application.properties

现在编译并运行:

bash
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

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"]

构建镜像:

bash
docker build -t hello-atlas:v1 .

启动容器:

bash
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 账号打通,不需要额外注册。

第一步:登录

bash
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin

GITHUB_TOKEN 是你之前在 GitHub Settings → Developer Settings → Personal access tokens 里创建的,权限需要 write:packages

第二步:给镜像打上仓库标签

bash
docker tag hello-atlas:v1 ghcr.io/YOUR_GITHUB_USERNAME/hello-atlas:v1

第三步:推送到远程仓库

bash
docker push ghcr.io/YOUR_GITHUB_USERNAME/hello-atlas:v1

推送成功后,任何人都能从仓库拉取你的镜像——只要有权限。


CI/CD:让机器替你走路

你手动推送了一次,成功了。但第二天你又改了一行代码——然后重复了六步操作:git pushmvn clean packagedocker builddocker pushssh serverdocker pulldocker restart……

第三天,你忘了一步。

线上挂了。用户看不到更新了。你手忙脚乱地翻终端历史,找上次是怎么部署的。

"我能不能写一个脚本,让我推完代码它就全自动走完?"你问。"我不想再手敲这七条命令了——我总会漏掉一条的。"

项目实战: 以下自动化流程等你开始频繁部署时再设置。现在先看懂概念,需要时回来抄配置。

手动构建→推送实在太原始了。每次改一行代码你都要重复:

bash
git push
mvn clean package
docker build
docker push
ssh server
docker pull
docker restart

太烦了。而且你一定会忘记某一步。

CI/CD 就是自动化这个流程——你把代码推到 GitHub,GitHub Actions 自动帮你编译、测试、打包、推送、部署。

在项目根目录创建 .github/workflows/deploy.yml

yaml
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,然后登录:

bash
# 登录到服务器
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"。

bash
# 找到谁占着 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:"我改了代码,但线上还是旧的"

最让人崩溃的事——你推了代码推送了镜像重启了容器,但用户看到的还是旧页面。

排查步骤:

bash
# 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 builddocker run 在本地启动容器,确保能通过 localhost:8080 访问。

  • 项目实战(选做):将应用推送到 GitHub Container Registry,然后在云服务器中部署它。这是你有了真实项目后的下一个大目标,但不是本章通关要求。

  • 观察

  1. 在容器内执行 curl localhost:8080 观察返回内容。
  2. 查看 docker logs 的输出,看看哪些是 Spring Boot 的启动日志,哪些是应用日志。
  3. 访问 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_HOMEmvn --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 自动化 → 部署上线。每多走一个阶段,你的代码就离"真正被别人用"近了一步。现在你知道了——部署不是魔法,是一条可以自动化走通的路。

下一站预告

工坊的师傅拍了拍你的肩:"出门朝东走,有个叫变量村的地方,那边有位老陈师傅——他能教你真本事。"

Built with VitePress | Software Systems Atlas