feat(deploy): add docker

This commit is contained in:
2026-02-11 16:30:54 +08:00
parent 03a1e6043d
commit d44a69bdaa
9 changed files with 230 additions and 27 deletions

31
.dockerignore Normal file
View File

@@ -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

View File

@@ -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"]

43
deploy/README.md Normal file
View File

@@ -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.

59
deploy/docker/Dockerfile Normal file
View File

@@ -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"]

View File

@@ -0,0 +1,9 @@
services:
iam-front:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile
env_file:
- ../../.env
ports:
- "${PORT}:${PORT}"

17
deploy/docker/start.sh Executable file
View File

@@ -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."

10
deploy/docker/stop.sh Executable file
View File

@@ -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."

53
deploy/validate-env.sh Normal file
View File

@@ -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"

View File

@@ -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).*)"],
};
}