chore:Initial addition of Docker related content
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# 多阶段构建 - 构建阶段
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装构建依赖
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
# 复制package文件
|
||||
COPY package*.json ./
|
||||
COPY pnpm-lock.yaml ./
|
||||
|
||||
# 安装pnpm
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# 安装依赖
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 设置环境变量
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
ENV NODE_ENV production
|
||||
|
||||
# 构建应用
|
||||
RUN pnpm build
|
||||
|
||||
# 生产阶段
|
||||
FROM node:18-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装运行时依赖
|
||||
RUN apk add --no-cache \
|
||||
curl \
|
||||
dumb-init \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# 创建非root用户
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S nextjs -u 1001 -G nodejs
|
||||
|
||||
# 复制构建产物
|
||||
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
|
||||
|
||||
# 设置环境变量
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
USER nextjs
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 3000
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||
|
||||
# 使用dumb-init作为PID 1
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
|
||||
# 启动应用
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,117 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const errors: string[] = [];
|
||||
let status = 'healthy';
|
||||
|
||||
// 基础健康信息
|
||||
const basicHealth = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: Math.floor((Date.now() - startTime) / 1000),
|
||||
service: 'privydrop-frontend',
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
};
|
||||
|
||||
// 检查后端API连接
|
||||
const backendHealth = await checkBackendHealth();
|
||||
if (backendHealth.status !== 'connected') {
|
||||
errors.push('Backend API connection failed');
|
||||
status = 'degraded';
|
||||
}
|
||||
|
||||
// 系统信息
|
||||
const systemInfo = {
|
||||
runtime: process.env.NEXT_RUNTIME || 'nodejs',
|
||||
nextjs: {
|
||||
version: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch
|
||||
},
|
||||
memory: process.memoryUsage ? {
|
||||
used: formatBytes(process.memoryUsage().heapUsed),
|
||||
total: formatBytes(process.memoryUsage().heapTotal),
|
||||
percent: Math.round((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100)
|
||||
} : null
|
||||
};
|
||||
|
||||
const detailedHealth = {
|
||||
...basicHealth,
|
||||
status,
|
||||
dependencies: {
|
||||
backend: backendHealth
|
||||
},
|
||||
system: systemInfo,
|
||||
...(errors.length > 0 && { errors })
|
||||
};
|
||||
|
||||
const httpStatus = status === 'healthy' ? 200 : 503;
|
||||
return NextResponse.json(detailedHealth, { status: httpStatus });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Detailed frontend health check error:', error);
|
||||
|
||||
return NextResponse.json({
|
||||
status: 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'privydrop-frontend',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, { status: 503 });
|
||||
}
|
||||
}
|
||||
|
||||
// 检查后端API健康状态
|
||||
async function checkBackendHealth() {
|
||||
try {
|
||||
const backendUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
const start = Date.now();
|
||||
|
||||
const response = await fetch(`${backendUrl}/health`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// 设置超时时间
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
const responseTime = Date.now() - start;
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return {
|
||||
status: 'connected',
|
||||
responseTime,
|
||||
backendUrl,
|
||||
backendService: data.service || 'unknown'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: 'error',
|
||||
responseTime,
|
||||
backendUrl,
|
||||
httpStatus: response.status,
|
||||
error: `HTTP ${response.status}`
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'disconnected',
|
||||
backendUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化字节数
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const health = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: Math.floor((Date.now() - startTime) / 1000),
|
||||
service: 'privydrop-frontend',
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
nextjs: {
|
||||
version: process.env.NEXT_RUNTIME || 'nodejs'
|
||||
}
|
||||
};
|
||||
|
||||
return NextResponse.json(health, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('Frontend health check error:', error);
|
||||
|
||||
return NextResponse.json({
|
||||
status: 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'privydrop-frontend',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, { status: 503 });
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,12 @@ const nextConfig = {
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
// 启用standalone输出模式,用于Docker部署
|
||||
output: 'standalone',
|
||||
// 禁用telemetry
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default withMDX(nextConfig);
|
||||
Reference in New Issue
Block a user