diff --git a/Dockerfile b/Dockerfile index b3ecd31c..8c6e73e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ -# 构建 Vue 静态文件 -FROM node:22-alpine AS build +# 构建 Vue 静态文件。CI 通过构建参数切换到内网基础镜像, +# 本地开发仍可使用 Docker Hub 默认值。 +ARG NODE_IMAGE=node:22-alpine +ARG NGINX_IMAGE=nginx:1.27-alpine +FROM ${NODE_IMAGE} AS build WORKDIR /app @@ -24,7 +27,7 @@ ENV VITE_CLIENT_WEB_URL=${VITE_CLIENT_WEB_URL} RUN pnpm build # 运行阶段:仅保留构建产物和 Nginx,不包含源码或 node_modules。 -FROM nginx:1.27-alpine +FROM ${NGINX_IMAGE} COPY nginx/default.conf.template /etc/nginx/templates/default.conf.template COPY docker/runtime-config.sh /docker-entrypoint.d/40-runtime-config.sh diff --git a/Jenkinsfile b/Jenkinsfile index 2afb4e67..4e107ca5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,10 @@ pipeline { environment { DEVELOPMENT_COMPOSE_FILE = 'docker-compose.development.yml' + REGISTRY_HOST = '172.31.198.7:10082' + REGISTRY_CREDENTIALS = 'gitea-registry-token' + NODE_BASE_IMAGE = '172.31.198.7:10082/html/node:22-alpine' + NGINX_BASE_IMAGE = '172.31.198.7:10082/html/nginx:1.27-alpine' } options { @@ -26,29 +30,53 @@ pipeline { stage('构建并部署测试前端') { when { expression { env.IS_DEVELOPMENT != 'true' } } steps { - sh ''' - set -eu - docker compose -f docker-compose.test.yml build --pull=false - docker compose -f docker-compose.test.yml up -d --remove-orphans - ''' + withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) { + sh ''' + set -eu + export DOCKER_CONFIG="${WORKSPACE}@tmp/docker-auth" + rm -rf "$DOCKER_CONFIG" + mkdir -p "$DOCKER_CONFIG" + cleanup_registry() { + docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true + rm -rf "$DOCKER_CONFIG" + } + trap cleanup_registry EXIT HUP INT TERM + printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" + docker compose -f docker-compose.test.yml build --pull=false + docker compose -f docker-compose.test.yml up -d --remove-orphans + ''' + } } } stage('构建并部署开发前端') { when { expression { env.IS_DEVELOPMENT == 'true' } } steps { - sh ''' - set -eu - image="admin-web-development:${SOURCE_TAG}" + withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) { + sh ''' + set -eu + export DOCKER_CONFIG="${WORKSPACE}@tmp/docker-auth" + rm -rf "$DOCKER_CONFIG" + mkdir -p "$DOCKER_CONFIG" + cleanup_registry() { + docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true + rm -rf "$DOCKER_CONFIG" + } + trap cleanup_registry EXIT HUP INT TERM + printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" + image="admin-web-development:${SOURCE_TAG}" - docker build \ - --build-arg VITE_CLIENT_WEB_URL=http://8.163.49.32:9002 \ - -t "$image" . - export IMAGE_TAG="$SOURCE_TAG" - docker compose -f "$DEVELOPMENT_COMPOSE_FILE" up -d --remove-orphans --pull never - docker compose -f "$DEVELOPMENT_COMPOSE_FILE" ps - docker compose -f "$DEVELOPMENT_COMPOSE_FILE" exec -T admin-web wget -qO- http://127.0.0.1/healthz - ''' + docker build \ + --build-arg NODE_IMAGE="$NODE_BASE_IMAGE" \ + --build-arg NGINX_IMAGE="$NGINX_BASE_IMAGE" \ + --build-arg VITE_CLIENT_WEB_URL=http://8.163.49.32:9002 \ + -t "$image" . + export IMAGE_TAG="$SOURCE_TAG" + docker compose -f "$DEVELOPMENT_COMPOSE_FILE" up -d --remove-orphans --pull never + docker compose -f "$DEVELOPMENT_COMPOSE_FILE" ps + docker compose -f "$DEVELOPMENT_COMPOSE_FILE" exec -T admin-web wget -qO- http://127.0.0.1/healthz + ''' + } } } diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 4310900a..c76791f1 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -3,6 +3,9 @@ services: build: context: . dockerfile: Dockerfile + args: + NODE_IMAGE: "${NODE_BASE_IMAGE:-node:22-alpine}" + NGINX_IMAGE: "${NGINX_BASE_IMAGE:-nginx:1.27-alpine}" image: admin-web-test:local container_name: admin-web-test restart: unless-stopped diff --git a/src/deployment/nginxConfig.test.ts b/src/deployment/nginxConfig.test.ts index a471eb7f..48fba67f 100644 --- a/src/deployment/nginxConfig.test.ts +++ b/src/deployment/nginxConfig.test.ts @@ -6,6 +6,7 @@ const config = readFileSync(resolve(process.cwd(), 'nginx/default.conf.template' const testCompose = readFileSync(resolve(process.cwd(), 'docker-compose.test.yml'), 'utf8') const developmentCompose = readFileSync(resolve(process.cwd(), 'docker-compose.development.yml'), 'utf8') const jenkinsfile = readFileSync(resolve(process.cwd(), 'Jenkinsfile'), 'utf8') +const dockerfile = readFileSync(resolve(process.cwd(), 'Dockerfile'), 'utf8') describe('admin nginx deployment config', () => { it('allows qualification uploads with multipart overhead through the API proxy', () => { @@ -46,4 +47,15 @@ describe('admin nginx deployment config', () => { expect(jenkinsfile).not.toContain('docker save') expect(jenkinsfile).not.toMatch(/\bssh\b/) }) + + it('uses authenticated internal base images in CI instead of pulling from Docker Hub', () => { + expect(dockerfile).toContain('ARG NODE_IMAGE=node:22-alpine') + expect(dockerfile).toContain('ARG NGINX_IMAGE=nginx:1.27-alpine') + expect(dockerfile).toContain('FROM ${NODE_IMAGE} AS build') + expect(dockerfile).toContain('FROM ${NGINX_IMAGE}') + expect(jenkinsfile).toContain("NODE_BASE_IMAGE = '172.31.198.7:10082/html/node:22-alpine'") + expect(jenkinsfile).toContain("NGINX_BASE_IMAGE = '172.31.198.7:10082/html/nginx:1.27-alpine'") + expect(jenkinsfile).toContain('withCredentials([usernamePassword(') + expect(jenkinsfile).toContain('export DOCKER_CONFIG="${WORKSPACE}@tmp/docker-auth"') + }) })