Publish verified Docker images to GHCR

Make deployments pull immutable CI-built images while keeping test failures diagnosable before any package is published.
This commit is contained in:
Rocky
2026-08-16 14:56:36 +08:00
parent 0af35d44f4
commit 41e2145334
7 changed files with 74 additions and 16 deletions
+2 -1
View File
@@ -4,7 +4,8 @@ PORT=8080
PUBLIC_BASE_URL=https://account.osglab.com
INVITE_BASE_URL=https://osglab.com/i
APP_STORE_URL=https://apps.apple.com/app/id0000000000
IMAGE_TAG=local
ACCOUNT_IMAGE=ghcr.io/hkgood/osg-account-server
IMAGE_TAG=main
ACCOUNT_BIND_PORT=18080
ACCOUNT_DOCKER_NETWORK=account-backend
+43
View File
@@ -4,6 +4,7 @@ on:
pull_request:
push:
branches: [main]
tags: ["v*"]
permissions:
contents: read
@@ -41,7 +42,49 @@ jobs:
run: docker compose -f compose.yaml config --quiet
- name: Test
run: ./gradlew --no-daemon clean test
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports-${{ github.run_id }}
path: |
build/reports/tests/test
build/test-results/test
if-no-files-found: ignore
- name: Build deployable JAR
run: ./gradlew --no-daemon buildFatJar
- name: Build container
run: docker build -t osg-account-server:ci .
publish:
if: github.event_name == 'push'
needs: verify
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: metadata
with:
images: ghcr.io/${{ github.repository_owner }}/osg-account-server
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,prefix=sha-
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+10 -2
View File
@@ -204,13 +204,21 @@ Deployment:
# Create this only when the existing MySQL is containerized and the network is absent.
docker network create --internal account-backend
# Authenticate once because the GHCR package is private.
echo "$GHCR_TOKEN" | docker login ghcr.io -u hkgood --password-stdin
# Put deployment values in an uncommitted .env or 1Panel secret/environment store.
docker compose config
docker compose up -d --build
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
curl --fail http://127.0.0.1:18080/health/ready
```
Every successful `main` CI run publishes `ghcr.io/hkgood/osg-account-server:main` plus an immutable
`sha-<commit>` tag. Production should pin a tested immutable tag in `IMAGE_TAG`; use `main` only for
initial staging. `GHCR_TOKEN` needs package-read permission and must not be stored in `.env`.
If MySQL runs directly on the host or another private server, keep the external network declaration
but set `DATABASE_URL` to a private hostname reachable from that network. The application also joins
the separate egress network for Apple and provider HTTPS calls. Never publish MySQL port 3306 to the
+5
View File
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
kotlin("jvm") version "2.4.10"
@@ -85,6 +86,10 @@ dependencies {
tasks.test {
useJUnitPlatform()
testLogging {
events("failed", "skipped")
exceptionFormat = TestExceptionFormat.FULL
}
finalizedBy(tasks.jacocoTestReport)
}
+2 -4
View File
@@ -1,9 +1,7 @@
services:
account-server:
build:
context: .
dockerfile: Dockerfile
image: osg-account-server:${IMAGE_TAG:-local}
image: ${ACCOUNT_IMAGE:-ghcr.io/hkgood/osg-account-server}:${IMAGE_TAG:-main}
pull_policy: always
restart: unless-stopped
init: true
user: "10001:10001"
+8 -4
View File
@@ -99,12 +99,16 @@ v3 WSS endpoint、资源 ID 和 API KeyDeepSeek 使用 HTTPS endpoint、已
## 5. 构建与启动
先检查变量插值。`docker compose config` 会展开秘密,不要把输出上传或粘贴到工单:
GitHub CI 在测试通过后发布私有镜像
`ghcr.io/hkgood/osg-account-server`。先使用仅有 `read:packages` 权限的部署令牌登录 GHCR
令牌不要写入 `.env`、Compose、1Panel 截图或 shell 历史。然后检查变量插值并拉取镜像。
`docker compose config` 会展开秘密,不要把输出上传或粘贴到工单:
```bash
./gradlew test
echo "$GHCR_TOKEN" | docker login ghcr.io -u hkgood --password-stdin
docker compose config --quiet
docker compose build --pull
docker compose pull
docker compose up -d
docker compose ps
curl --fail http://127.0.0.1:18080/health/ready
@@ -175,8 +179,8 @@ curl -i https://osglab.com/i/AbCdEf0123456789_-AbCd
更新前备份 MySQL 并记录当前镜像标签。使用不可变标签构建:
```bash
IMAGE_TAG=<release-tag> docker compose build
IMAGE_TAG=<release-tag> docker compose up -d
IMAGE_TAG=sha-<commit> docker compose pull
IMAGE_TAG=sha-<commit> docker compose up -d
```
Flyway 迁移只向前执行。若新版本包含数据库迁移,应用镜像回滚不等于数据库回滚;应先按迁移影响
@@ -24,6 +24,8 @@ class DeploymentConsistencyTest : FunSpec({
val compose = root.read("compose.yaml")
compose shouldContain "127.0.0.1:\${ACCOUNT_BIND_PORT:-18080}:8080"
compose shouldContain "ghcr.io/hkgood/osg-account-server"
compose shouldContain "pull_policy: always"
compose shouldContain "external: true"
compose shouldContain "account-egress:"
compose shouldContain "user: \"10001:10001\""
@@ -63,6 +65,8 @@ class DeploymentConsistencyTest : FunSpec({
ci shouldContain "docker compose -f compose.yaml config --quiet"
ci shouldContain "./gradlew --no-daemon clean test"
ci shouldContain "./gradlew --no-daemon buildFatJar"
ci shouldContain "docker/build-push-action@v6"
ci shouldContain "packages: write"
ci shouldNotContain "3306:3306"
ci shouldNotContain "TEST_DB_"
}
@@ -91,11 +95,6 @@ private val EXPECTED_PUBLIC_PATHS = setOf(
"/v1/credits/balance",
"/v1/credits/ledger",
"/v1/credits/rates",
"/v1/credits/reservations",
"/v1/credits/reservations/{reservationId}",
"/v1/credits/reservations/{reservationId}/settle",
"/v1/credits/reservations/{reservationId}/release",
"/v1/credits/reservations/{reservationId}/refund",
"/v1/referrals",
"/v1/referrals/me",
"/v1/referrals/code",