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:
+2
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -99,12 +99,16 @@ v3 WSS endpoint、资源 ID 和 API Key;DeepSeek 使用 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",
|
||||
|
||||
Reference in New Issue
Block a user