用 Gitea、Jenkins 和 Docker 发布个人博客
把 Astro 静态博客打包成 Docker 容器,通过 Cloudflare Tunnel 暴露到公网。
个人技术博客不一定要放在托管平台上。如果服务器上已经有 Gitea、Jenkins、Docker 和 cloudflared tunnel,就可以搭出一条很清楚的发布链路:代码自己管,构建自己跑,博客以容器方式运行,公网入口交给 Cloudflare。
本地写作 -> Gitea -> Jenkins -> Astro build -> Docker image -> Blog container -> Cloudflare Tunnel
这套方案里,Astro 只负责生成静态文件,Docker 镜像负责承载这些静态文件,容器内的 Nginx 负责提供访问。宿主机上是否直装 Nginx 不重要,因为 tunnel 可以直接指向博客容器暴露出来的本机端口。
第一版目标
先让博客具备最核心的发布能力:
- Markdown 写文章
- Astro 静态构建
- Jenkins 自动构建
- Docker 镜像封装
- Docker Compose 启动博客容器
- Cloudflare Tunnel 指向博客容器端口
对静态博客来说,这条链路已经足够稳定。后面如果要加评论、搜索、统计,再单独扩展。
运行链路
博客容器只监听服务器本机端口:
127.0.0.1:3001 -> container:80
公网访问链路是:
浏览器 -> Cloudflare -> cloudflared -> localhost:3001 -> blog container -> nginx -> dist
这样做的好处是容器端口不直接暴露公网,HTTPS 和公网入口仍然由 Cloudflare 处理。
Docker 镜像
博客先在 Jenkins workspace 里执行构建:
npm ci
npm run build
构建完成后会生成 dist/。然后用一个很薄的 Nginx 镜像承载它:
FROM nginx:alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY dist /usr/share/nginx/html
Nginx 配置只需要服务静态文件:
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Docker Compose
deploy/docker-compose.yml 负责启动博客容器:
services:
blog-frontend:
image: blog-frontend-astro:latest
container_name: blog-frontend-astro
restart: unless-stopped
ports:
- "127.0.0.1:3001:80"
因为镜像已经包含 dist/ 和 Nginx 配置,所以这里不需要再挂载站点目录。
Jenkins 流水线
Jenkinsfile 的核心流程是:
pipeline {
agent any
tools {
nodejs 'NodeJS 26.1.0'
}
environment {
IMAGE_NAME = 'blog-frontend-astro:latest'
COMPOSE_FILE = 'deploy/docker-compose.yml'
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Deploy') {
steps {
sh '''
docker build -t "$IMAGE_NAME" .
docker compose -f "$COMPOSE_FILE" up -d --force-recreate
'''
}
}
}
}
Jenkins 运行在 Docker 容器里时,需要能调用宿主机 Docker。常见做法是给 Jenkins 容器挂载 Docker socket,并在 Jenkins 镜像里安装 Docker CLI:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
这样 Jenkins 容器里执行的 docker build 和 docker compose up -d,实际操作的是宿主机 Docker。
cloudflared tunnel
tunnel 只需要把 zengqing.top 指向博客容器端口:
http://localhost:3001
如果 Cloudflare 页面提示 DNS 记录已存在,只要当前域名已经正确指向 tunnel,就不需要重复创建 DNS 记录。
上线前检查
第一次上线前建议检查:
astro.config.mjs里的site是https://zengqing.top- Jenkins 能成功执行
npm ci - Jenkins 能成功执行
npm run build - Jenkins 容器里有 Docker CLI
- Jenkins 容器挂载了
/var/run/docker.sock docker ps能看到blog-frontend-astro- 服务器本机访问
http://localhost:3001正常 - Cloudflare Tunnel 的
zengqing.top指向http://localhost:3001
整条链路跑通之后,日常写文章只需要提交代码,剩下的构建和发布交给 Jenkins。