ci(deploy): support pulling published docker images
This commit is contained in:
@@ -9,6 +9,7 @@ node_modules
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
.deploy.env
|
||||||
!.env.example
|
!.env.example
|
||||||
env-example
|
env-example
|
||||||
|
|
||||||
|
|||||||
+64
-6
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Post-receive hook: push 后自动构建 Docker 镜像并重启容器
|
# Post-receive hook: push 后自动部署容器
|
||||||
|
# 默认在服务器本机构建镜像;配置 .deploy.env 后也可从镜像仓库拉取 CI 构建好的镜像。
|
||||||
|
|
||||||
REPO_TOPLEVEL=""
|
REPO_TOPLEVEL=""
|
||||||
LOG_FILE=""
|
LOG_FILE=""
|
||||||
@@ -8,6 +9,8 @@ DEPLOY_ENV=""
|
|||||||
ENV_FILE=""
|
ENV_FILE=""
|
||||||
START_PORT=""
|
START_PORT=""
|
||||||
IMAGE_TAG=""
|
IMAGE_TAG=""
|
||||||
|
IMAGE_SOURCE=""
|
||||||
|
REMOTE_IMAGE_TAG=""
|
||||||
CONTAINER_NAME=""
|
CONTAINER_NAME=""
|
||||||
COMPOSE_PROJECT_NAME=""
|
COMPOSE_PROJECT_NAME=""
|
||||||
COMPOSE_EXIT=0
|
COMPOSE_EXIT=0
|
||||||
@@ -45,6 +48,19 @@ sync_worktree() {
|
|||||||
git reset --hard HEAD >> "$LOG_FILE" 2>&1
|
git reset --hard HEAD >> "$LOG_FILE" 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load_deploy_config() {
|
||||||
|
if [ ! -f ".deploy.env" ]; then
|
||||||
|
echo "=== deploy config: .deploy.env not found, use defaults ===" >> "$LOG_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== deploy config: loading .deploy.env ===" >> "$LOG_FILE"
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. ./.deploy.env
|
||||||
|
set +a
|
||||||
|
}
|
||||||
|
|
||||||
copy_env_by_branch() {
|
copy_env_by_branch() {
|
||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||||
echo "=== branch: $CURRENT_BRANCH ===" >> "$LOG_FILE"
|
echo "=== branch: $CURRENT_BRANCH ===" >> "$LOG_FILE"
|
||||||
@@ -81,7 +97,26 @@ copy_env_by_branch() {
|
|||||||
|
|
||||||
prepare_compose_env() {
|
prepare_compose_env() {
|
||||||
SHORT_SHA="$(git rev-parse --short HEAD)"
|
SHORT_SHA="$(git rev-parse --short HEAD)"
|
||||||
IMAGE_TAG="cozsweet-web:${DEPLOY_ENV}-${SHORT_SHA}"
|
IMAGE_SOURCE="${COZSWEET_DEPLOY_IMAGE_SOURCE:-build}"
|
||||||
|
REMOTE_IMAGE_TAG="${COZSWEET_REMOTE_IMAGE_TAG:-${DEPLOY_ENV}-${SHORT_SHA}}"
|
||||||
|
|
||||||
|
case "$IMAGE_SOURCE" in
|
||||||
|
build)
|
||||||
|
IMAGE_TAG="cozsweet-web:${DEPLOY_ENV}-${SHORT_SHA}"
|
||||||
|
;;
|
||||||
|
pull)
|
||||||
|
if [ -z "${COZSWEET_REGISTRY_IMAGE:-}" ]; then
|
||||||
|
echo "=== missing COZSWEET_REGISTRY_IMAGE for pull deploy ===" >> "$LOG_FILE"
|
||||||
|
exit 64
|
||||||
|
fi
|
||||||
|
IMAGE_TAG="${COZSWEET_REGISTRY_IMAGE}:${REMOTE_IMAGE_TAG}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "=== unsupported COZSWEET_DEPLOY_IMAGE_SOURCE: $IMAGE_SOURCE ===" >> "$LOG_FILE"
|
||||||
|
exit 64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
CONTAINER_NAME="cozsweet-web-${DEPLOY_ENV}"
|
CONTAINER_NAME="cozsweet-web-${DEPLOY_ENV}"
|
||||||
COMPOSE_PROJECT_NAME="cozsweet-web-${DEPLOY_ENV}"
|
COMPOSE_PROJECT_NAME="cozsweet-web-${DEPLOY_ENV}"
|
||||||
|
|
||||||
@@ -95,8 +130,10 @@ prepare_compose_env() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
echo "=== deploy env: $DEPLOY_ENV ==="
|
echo "=== deploy env: $DEPLOY_ENV ==="
|
||||||
|
echo "=== image source: $IMAGE_SOURCE ==="
|
||||||
echo "=== env file: $COZSWEET_ENV_FILE ==="
|
echo "=== env file: $COZSWEET_ENV_FILE ==="
|
||||||
echo "=== image: $COZSWEET_IMAGE ==="
|
echo "=== image: $COZSWEET_IMAGE ==="
|
||||||
|
echo "=== remote image tag: $REMOTE_IMAGE_TAG ==="
|
||||||
echo "=== container: $COZSWEET_CONTAINER_NAME ==="
|
echo "=== container: $COZSWEET_CONTAINER_NAME ==="
|
||||||
echo "=== host port: $COZSWEET_HOST_PORT ==="
|
echo "=== host port: $COZSWEET_HOST_PORT ==="
|
||||||
echo "=== compose project: $COMPOSE_PROJECT_NAME ==="
|
echo "=== compose project: $COMPOSE_PROJECT_NAME ==="
|
||||||
@@ -128,9 +165,22 @@ run_container_build() {
|
|||||||
return $COMPOSE_EXIT
|
return $COMPOSE_EXIT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pull_container_image() {
|
||||||
|
echo "=== docker pull START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||||
|
docker_compose pull web >> "$LOG_FILE" 2>&1
|
||||||
|
COMPOSE_EXIT=$?
|
||||||
|
echo "=== docker pull EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||||
|
echo "" >> "$LOG_FILE"
|
||||||
|
return $COMPOSE_EXIT
|
||||||
|
}
|
||||||
|
|
||||||
launch_container() {
|
launch_container() {
|
||||||
echo "=== docker compose up START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
echo "=== docker compose up START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||||
docker_compose up -d --remove-orphans web >> "$LOG_FILE" 2>&1
|
if [ "$IMAGE_SOURCE" = "pull" ]; then
|
||||||
|
docker_compose up -d --remove-orphans --no-build web >> "$LOG_FILE" 2>&1
|
||||||
|
else
|
||||||
|
docker_compose up -d --remove-orphans web >> "$LOG_FILE" 2>&1
|
||||||
|
fi
|
||||||
COMPOSE_EXIT=$?
|
COMPOSE_EXIT=$?
|
||||||
echo "=== docker compose up EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
echo "=== docker compose up EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||||
echo "" >> "$LOG_FILE"
|
echo "" >> "$LOG_FILE"
|
||||||
@@ -147,12 +197,20 @@ main() {
|
|||||||
write_metadata_header
|
write_metadata_header
|
||||||
|
|
||||||
sync_worktree
|
sync_worktree
|
||||||
|
load_deploy_config
|
||||||
copy_env_by_branch
|
copy_env_by_branch
|
||||||
prepare_compose_env
|
prepare_compose_env
|
||||||
|
|
||||||
if ! run_container_build; then
|
if [ "$IMAGE_SOURCE" = "pull" ]; then
|
||||||
log_abort "docker build failed, old container kept running"
|
if ! pull_container_image; then
|
||||||
exit $COMPOSE_EXIT
|
log_abort "docker pull failed, old container kept running"
|
||||||
|
exit $COMPOSE_EXIT
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! run_container_build; then
|
||||||
|
log_abort "docker build failed, old container kept running"
|
||||||
|
exit $COMPOSE_EXIT
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! launch_container; then
|
if ! launch_container; then
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ yarn-error.log*
|
|||||||
.env.local
|
.env.local
|
||||||
.env.development
|
.env.development
|
||||||
.env.production
|
.env.production
|
||||||
|
.deploy.env
|
||||||
|
|
||||||
# deploy archives (scripts/deploy/_deploy_lib.sh 的 archive() 生成)
|
# deploy archives (scripts/deploy/_deploy_lib.sh 的 archive() 生成)
|
||||||
archives/
|
archives/
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# 自动构建与自动重启流程
|
# 自动构建与自动重启流程
|
||||||
|
|
||||||
本文档说明当前项目通过 Git remote + `post-receive` hook 实现自动构建、自动重启的流程。当前服务器端运行方式已经容器化,核心文件为 [.githooks/post-receive](../.githooks/post-receive)、[Dockerfile](../Dockerfile) 和 [docker-compose.yml](../docker-compose.yml)。
|
本文档说明当前项目通过 Git remote + `post-receive` hook 实现自动部署、自动重启的流程。当前服务器端运行方式已经容器化,核心文件为 [.githooks/post-receive](../.githooks/post-receive)、[Dockerfile](../Dockerfile) 和 [docker-compose.yml](../docker-compose.yml)。
|
||||||
|
|
||||||
|
服务器默认仍会本地构建 Docker 镜像;如果服务器工作树中存在 `.deploy.env` 且配置 `COZSWEET_DEPLOY_IMAGE_SOURCE=pull`,则会改为从镜像仓库拉取由 Gitea Actions 构建好的镜像。
|
||||||
|
|
||||||
## 部署入口
|
## 部署入口
|
||||||
|
|
||||||
@@ -73,9 +75,10 @@ production root@43.106.13.130:/root/cozsweet-repos/main
|
|||||||
5. 根据当前分支复制环境变量文件。
|
5. 根据当前分支复制环境变量文件。
|
||||||
6. 根据当前分支选择 Next.js 启动端口。
|
6. 根据当前分支选择 Next.js 启动端口。
|
||||||
7. 根据分支导出 Docker Compose 所需变量,包括镜像名、容器名、env 文件和端口。
|
7. 根据分支导出 Docker Compose 所需变量,包括镜像名、容器名、env 文件和端口。
|
||||||
8. 执行 `docker compose build web` 构建环境专属镜像。
|
8. 如果使用默认 `build` 模式,执行 `docker compose build web` 构建环境专属镜像。
|
||||||
9. 如果镜像构建失败,写入失败日志并中止,旧容器继续运行。
|
9. 如果使用 `pull` 模式,执行 `docker compose pull web` 拉取 CI 已发布镜像。
|
||||||
10. 如果镜像构建成功,执行 `docker compose up -d --remove-orphans web` 重建并启动容器。
|
10. 如果镜像构建或拉取失败,写入失败日志并中止,旧容器继续运行。
|
||||||
|
11. 如果镜像准备成功,执行 `docker compose up -d --remove-orphans web` 重建并启动容器。
|
||||||
|
|
||||||
## 分支、环境变量、端口映射
|
## 分支、环境变量、端口映射
|
||||||
|
|
||||||
@@ -88,6 +91,28 @@ production root@43.106.13.130:/root/cozsweet-repos/main
|
|||||||
| `dev` | `env-example/.env.development.example` → `.env.local` | `9135` | `cozsweet-web-dev` | `cozsweet-web:dev-<commit>` |
|
| `dev` | `env-example/.env.development.example` → `.env.local` | `9135` | `cozsweet-web-dev` | `cozsweet-web:dev-<commit>` |
|
||||||
| 其他分支 | 使用默认 `.env.local` | `9135` | `cozsweet-web-<branch>` | `cozsweet-web:<branch>-<commit>` |
|
| 其他分支 | 使用默认 `.env.local` | `9135` | `cozsweet-web-<branch>` | `cozsweet-web:<branch>-<commit>` |
|
||||||
|
|
||||||
|
如果 `.deploy.env` 配置为 pull 模式,镜像标签会从本地标签切换为远程仓库标签:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COZSWEET_DEPLOY_IMAGE_SOURCE=pull
|
||||||
|
COZSWEET_REGISTRY_IMAGE=registry.example.com/cozsweet/cozsweet-web
|
||||||
|
```
|
||||||
|
|
||||||
|
默认远程镜像 tag 为 `<deploy_env>-<short_sha>`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
registry.example.com/cozsweet/cozsweet-web:test-<commit>
|
||||||
|
registry.example.com/cozsweet/cozsweet-web:prod-<commit>
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以在服务器本地 `.deploy.env` 中覆盖为 latest tag:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COZSWEET_REMOTE_IMAGE_TAG=prod-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
推荐生产发布使用默认精确 tag,避免 `latest` 因缓存或并发发布导致版本不可追踪。
|
||||||
|
|
||||||
本地生产发布脚本和服务器端 `post-receive` 已统一使用 `.env.production`。Next.js 在生产构建时会读取 `.env.production`;测试和开发发布仍使用 `.env.local`。
|
本地生产发布脚本和服务器端 `post-receive` 已统一使用 `.env.production`。Next.js 在生产构建时会读取 `.env.production`;测试和开发发布仍使用 `.env.local`。
|
||||||
|
|
||||||
本地部署脚本清除 Cloudflare CDN 缓存时,也会按当前分支读取 env:`main` 优先读取 `.env.production`,其他分支优先读取 `.env.local`。如果优先文件不存在或缺少 `CF_ZONE_ID` / `CF_API_TOKEN`,会尝试读取另一个文件作为兜底。
|
本地部署脚本清除 Cloudflare CDN 缓存时,也会按当前分支读取 env:`main` 优先读取 `.env.production`,其他分支优先读取 `.env.local`。如果优先文件不存在或缺少 `CF_ZONE_ID` / `CF_API_TOKEN`,会尝试读取另一个文件作为兜底。
|
||||||
@@ -127,6 +152,34 @@ cd /root/cozsweet-repos/main
|
|||||||
|
|
||||||
后续生产发布会自动使用新的容器化 hook。
|
后续生产发布会自动使用新的容器化 hook。
|
||||||
|
|
||||||
|
## 镜像来源
|
||||||
|
|
||||||
|
### 默认:服务器本机构建
|
||||||
|
|
||||||
|
未配置 `.deploy.env` 时,`post-receive` 使用原有模式:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build web
|
||||||
|
docker compose up -d --remove-orphans web
|
||||||
|
```
|
||||||
|
|
||||||
|
### 推荐:CI 构建,服务器拉取
|
||||||
|
|
||||||
|
Gitea Actions 通过 [.gitea/workflows/docker-image.yml](../.gitea/workflows/docker-image.yml) 构建并推送镜像。服务器只负责拉取镜像:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull web
|
||||||
|
docker compose up -d --remove-orphans --no-build web
|
||||||
|
```
|
||||||
|
|
||||||
|
服务器需要提前执行一次 registry 登录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker login REGISTRY_HOST
|
||||||
|
```
|
||||||
|
|
||||||
|
服务器本地 `.deploy.env` 不提交到仓库,示例见 [env-example/.deploy.env.example](../env-example/.deploy.env.example)。
|
||||||
|
|
||||||
## 容器构建机制
|
## 容器构建机制
|
||||||
|
|
||||||
Docker 构建使用 Next.js standalone 输出:
|
Docker 构建使用 Next.js standalone 输出:
|
||||||
@@ -145,14 +198,20 @@ Docker 构建使用 Next.js standalone 输出:
|
|||||||
|
|
||||||
重启由 Docker Compose 实现:
|
重启由 Docker Compose 实现:
|
||||||
|
|
||||||
1. `post-receive` 先执行 `docker compose build web`。
|
1. `post-receive` 先准备镜像:默认执行 `docker compose build web`,pull 模式执行 `docker compose pull web`。
|
||||||
2. 构建失败时直接中止,旧容器继续运行。
|
2. 镜像准备失败时直接中止,旧容器继续运行。
|
||||||
3. 构建成功后执行:
|
3. 镜像准备成功后执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --remove-orphans web
|
docker compose up -d --remove-orphans web
|
||||||
```
|
```
|
||||||
|
|
||||||
|
pull 模式下会额外加上 `--no-build`,确保服务器不会意外本地构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --remove-orphans --no-build web
|
||||||
|
```
|
||||||
|
|
||||||
4. Compose 会使用新镜像重建服务容器。
|
4. Compose 会使用新镜像重建服务容器。
|
||||||
5. 容器配置了 `restart: unless-stopped`,容器异常退出后由 Docker 自动拉起。
|
5. 容器配置了 `restart: unless-stopped`,容器异常退出后由 Docker 自动拉起。
|
||||||
6. 容器内固定监听 `3000`,宿主机端口按分支映射为 `9135` 或 `9185`。
|
6. 容器内固定监听 `3000`,宿主机端口按分支映射为 `9135` 或 `9185`。
|
||||||
@@ -190,13 +249,20 @@ git branch --show-current
|
|||||||
git rev-parse --short HEAD
|
git rev-parse --short HEAD
|
||||||
```
|
```
|
||||||
|
|
||||||
镜像构建失败时,日志中会出现:
|
镜像构建或拉取失败时,日志中会出现:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
docker build EXIT_CODE=<非 0>
|
docker build EXIT_CODE=<非 0>
|
||||||
ABORTED ... (docker build failed, old container kept running)
|
ABORTED ... (docker build failed, old container kept running)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
或:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docker pull EXIT_CODE=<非 0>
|
||||||
|
ABORTED ... (docker pull failed, old container kept running)
|
||||||
|
```
|
||||||
|
|
||||||
这种情况下不会停止旧容器,也不会启动新容器。
|
这种情况下不会停止旧容器,也不会启动新容器。
|
||||||
|
|
||||||
容器启动失败时,日志中会出现:
|
容器启动失败时,日志中会出现:
|
||||||
|
|||||||
@@ -79,3 +79,39 @@ docker image ls | grep cozsweet-web
|
|||||||
```
|
```
|
||||||
|
|
||||||
如果使用 Gitea 自带 Container Registry,需要确认 Gitea 服务已开启 packages / container registry,并且 `REGISTRY_HOST` 与 Docker 登录地址一致。
|
如果使用 Gitea 自带 Container Registry,需要确认 Gitea 服务已开启 packages / container registry,并且 `REGISTRY_HOST` 与 Docker 登录地址一致。
|
||||||
|
|
||||||
|
## 部署服务器拉取镜像
|
||||||
|
|
||||||
|
部署服务器需要先登录镜像仓库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker login REGISTRY_HOST
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在部署服务器工作树中创建 `.deploy.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp env-example/.deploy.env.example .deploy.env
|
||||||
|
```
|
||||||
|
|
||||||
|
配置示例:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COZSWEET_DEPLOY_IMAGE_SOURCE=pull
|
||||||
|
COZSWEET_REGISTRY_IMAGE=registry.example.com/cozsweet/cozsweet-web
|
||||||
|
```
|
||||||
|
|
||||||
|
`post-receive` 会根据当前分支和 commit 自动拉取:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
registry.example.com/cozsweet/cozsweet-web:test-<short_sha>
|
||||||
|
registry.example.com/cozsweet/cozsweet-web:prod-<short_sha>
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要改用 latest tag,可以在服务器 `.deploy.env` 中显式设置:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COZSWEET_REMOTE_IMAGE_TAG=prod-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
推荐优先使用默认的 `<env>-<short_sha>` 精确 tag,这样发布和回滚都更可控。
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Server-only deploy configuration.
|
||||||
|
# Copy this file to .deploy.env on the deployment server when you want the
|
||||||
|
# post-receive hook to pull CI-built images instead of building locally.
|
||||||
|
|
||||||
|
# build: build Docker image on the deployment server.
|
||||||
|
# pull: pull Docker image from registry and restart the container.
|
||||||
|
COZSWEET_DEPLOY_IMAGE_SOURCE=pull
|
||||||
|
|
||||||
|
# Full image repository without tag.
|
||||||
|
# Example:
|
||||||
|
# COZSWEET_REGISTRY_IMAGE=registry.example.com/cozsweet/cozsweet-web
|
||||||
|
COZSWEET_REGISTRY_IMAGE=
|
||||||
|
|
||||||
|
# Optional. Defaults to <deploy_env>-<short_sha>, for example:
|
||||||
|
# - test-abc1234 on test branch
|
||||||
|
# - prod-abc1234 on main branch
|
||||||
|
#
|
||||||
|
# You may set this to test-latest or prod-latest for latest-tag deploys.
|
||||||
|
# COZSWEET_REMOTE_IMAGE_TAG=
|
||||||
Reference in New Issue
Block a user