From d44a69bdaa8c60b0c984cc005aad4fded73d15fa Mon Sep 17 00:00:00 2001 From: shay7sev Date: Wed, 11 Feb 2026 16:30:54 +0800 Subject: [PATCH] feat(deploy): add docker --- .dockerignore | 31 +++++++++++++++++ Dockerfile | 19 ---------- deploy/README.md | 43 +++++++++++++++++++++++ deploy/docker/Dockerfile | 59 ++++++++++++++++++++++++++++++++ deploy/docker/docker-compose.yml | 9 +++++ deploy/docker/start.sh | 17 +++++++++ deploy/docker/stop.sh | 10 ++++++ deploy/validate-env.sh | 53 ++++++++++++++++++++++++++++ src/proxy.ts | 16 ++++----- 9 files changed, 230 insertions(+), 27 deletions(-) create mode 100644 .dockerignore delete mode 100644 Dockerfile create mode 100644 deploy/README.md create mode 100644 deploy/docker/Dockerfile create mode 100644 deploy/docker/docker-compose.yml create mode 100755 deploy/docker/start.sh create mode 100755 deploy/docker/stop.sh create mode 100644 deploy/validate-env.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5049c7b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Dependencies +node_modules +.pnpm-store + +# Next.js build output +.next +out +build + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Debug logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# System files +.DS_Store +*.pem + +# IDEs +.idea +.vscode +*.swp +*.swo diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e3b0d9c..0000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM node:20-alpine AS deps -WORKDIR /app -COPY package.json ./ -RUN npm install - -FROM node:20-alpine AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . -RUN npm run build - -FROM node:20-alpine AS runner -WORKDIR /app -ENV NODE_ENV=production -ENV PORT=6020 -ENV HOSTNAME=0.0.0.0 -COPY --from=builder /app/.next/standalone ./ -COPY --from=builder /app/.next/static ./.next/static -CMD ["node", "server.js"] diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..4f61239 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,43 @@ +# IAM Front Deployment Guide + +## 1. Prerequisites + +- Docker & Docker Compose installed +- `iam-service` running (and accessible from this host) +- `.env` file configured (copied from `.env.example`) + +## 2. Configuration (.env) + +Ensure `.env` exists in `iam-front/` root. + +**Critical Variables:** + +- `IAM_SERVICE_BASE_URL`: URL to access iam-service. + - **Docker Note**: Do NOT use `localhost` or `127.0.0.1`. Use the host IP (e.g. `http://192.168.1.100:3000`). +- `CAPTCHA_SECRET`: Secure random string for cookie signing. +- `PORT`: Optional, defaults to 6020. + +## 3. Deploy + +```bash +cd deploy/docker +bash start.sh +``` + +This will: +1. Validate `.env` configuration (checks for localhost issues). +2. Build the Docker image. +3. Start the container with ports mapped (default 6020). + +## 4. Stop + +```bash +cd deploy/docker +bash stop.sh +``` + +## 5. Notes + +- This deployment only includes the `iam-front` service. +- It assumes `iam-service` is deployed separately. +- No database or Redis is required for the frontend. diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile new file mode 100644 index 0000000..c0abd20 --- /dev/null +++ b/deploy/docker/Dockerfile @@ -0,0 +1,59 @@ +# ========================================= +# Stage 1: Install dependencies (with pnpm) +# ========================================= +FROM node:20-alpine AS deps +WORKDIR /app + +# Enable pnpm via corepack and configure registry mirror +RUN corepack enable && corepack prepare pnpm@latest --activate \ + && npm config set registry https://registry.npmmirror.com/ + +# Copy only package files to cache dependency installation +COPY package.json pnpm-lock.yaml* ./ + +# Install dependencies (frozen-lockfile ensures reproducibility) +RUN pnpm install --frozen-lockfile --prod=false + +# ========================================= +# Stage 2: Builder (build Next.js app) +# ========================================= +FROM node:20-alpine AS builder +WORKDIR /app + +# Enable pnpm for potential script usage +RUN corepack enable && corepack prepare pnpm@latest --activate + +# Copy dependencies from deps stage +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build the application +# Note: Next.js telemetry is disabled via env if needed, or disable in next.config.js +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm run build + +# ========================================= +# Stage 3: Runner (Production image) +# ========================================= +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=6020 +ENV HOSTNAME="0.0.0.0" + +# Create non-root user for security +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +# Copy only necessary files for standalone mode +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 6020 + +CMD ["node", "server.js"] diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 0000000..9f0ca53 --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,9 @@ +services: + iam-front: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile + env_file: + - ../../.env + ports: + - "${PORT}:${PORT}" diff --git a/deploy/docker/start.sh b/deploy/docker/start.sh new file mode 100755 index 0000000..a4a063e --- /dev/null +++ b/deploy/docker/start.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +# 1. Validate environment +export DEPLOY_TARGET=docker +bash ../validate-env.sh + +# 2. Start +echo "Starting iam-front..." +# Removed --remove-orphans to prevent deleting containers from other compose projects +# if they share the same project name (which defaults to folder name "docker") +docker compose --env-file ../../.env -p iam-front up -d --build + +echo "iam-front is running." diff --git a/deploy/docker/stop.sh b/deploy/docker/stop.sh new file mode 100755 index 0000000..3d146dc --- /dev/null +++ b/deploy/docker/stop.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +echo "Stopping iam-front..." +docker compose --env-file ../../.env -p iam-front down + +echo "iam-front stopped." diff --git a/deploy/validate-env.sh b/deploy/validate-env.sh new file mode 100644 index 0000000..dc52394 --- /dev/null +++ b/deploy/validate-env.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +# 路径修正:validate-env.sh 在 deploy/ 目录下,而 .env 在项目根目录 +# 项目根目录 = deploy/../ = ../ +ENV_FILE="../.env" + +if [ ! -f "$ENV_FILE" ]; then + echo "Error: .env file not found at $(readlink -f "$ENV_FILE")" + echo "Please copy .env.example to .env and configure it." + exit 1 +fi + +echo "Validating iam-front .env configuration..." + +# Helper to read env var +get_env() { + local key=$1 + local val + val=$(grep "^${key}=" "$ENV_FILE" | cut -d= -f2- | tr -d '"' | tr -d "'") + echo "$val" +} + +# 1. 检查必要变量 +REQUIRED_VARS=( + "IAM_SERVICE_BASE_URL" + "CAPTCHA_SECRET" +) + +for var in "${REQUIRED_VARS[@]}"; do + val=$(get_env "$var") + if [ -z "$val" ]; then + echo "Error: $var is required in .env" + exit 1 + fi +done + +# 2. 检查 Docker 模式下 localhost +DEPLOY_TARGET="${DEPLOY_TARGET:-}" +if [ "$DEPLOY_TARGET" == "docker" ]; then + IAM_URL=$(get_env "IAM_SERVICE_BASE_URL") + if [[ "$IAM_URL" == *"localhost"* ]] || [[ "$IAM_URL" == *"127.0.0.1"* ]]; then + echo "Error: IAM_SERVICE_BASE_URL contains localhost/127.0.0.1" + echo "In Docker, this refers to the container itself." + echo "Please use the host IP or docker-compose service name (if in same network)." + exit 1 + fi +fi + +echo "iam-front .env validation OK" diff --git a/src/proxy.ts b/src/proxy.ts index a736ffc..7d21d61 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,17 +1,17 @@ -import { NextRequest, NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server" export function proxy(req: NextRequest) { - if (process.env.NODE_ENV === "production") { - const proto = req.headers.get("x-forwarded-proto"); + if (process.env.NEXT_PUBLIC_NODE_ENV === "production") { + const proto = req.headers.get("x-forwarded-proto") if (proto && proto !== "https") { - const url = req.nextUrl.clone(); - url.protocol = "https"; - return NextResponse.redirect(url, 308); + const url = req.nextUrl.clone() + url.protocol = "https" + return NextResponse.redirect(url, 308) } } - return NextResponse.next(); + return NextResponse.next() } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], -}; +}