更新dockerfile
This commit is contained in:
35
Dockerfile
35
Dockerfile
@@ -15,7 +15,7 @@ RUN pnpm install --frozen-lockfile
|
||||
# Copy frontend source
|
||||
COPY web/ ./
|
||||
|
||||
# Build frontend
|
||||
# Build frontend (SSR with adapter-node)
|
||||
RUN pnpm build
|
||||
|
||||
# Stage 2: Build backend
|
||||
@@ -29,14 +29,12 @@ RUN dart pub get
|
||||
|
||||
# Copy app source code and compile
|
||||
COPY bin/ ./bin/
|
||||
COPY --from=frontend-build /app/web/build ./web/build/
|
||||
|
||||
RUN dart compile exe bin/server.dart -o bin/server
|
||||
|
||||
# Stage 3: Runtime image with libvips
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
# Stage 3: Runtime image with Node.js, libvips and Dart runtime
|
||||
FROM node:22-bookworm-slim AS runtime
|
||||
|
||||
# Install libvips and ca-certificates
|
||||
# Install libvips, ca-certificates and other dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libvips42 \
|
||||
ca-certificates \
|
||||
@@ -45,21 +43,32 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the AOT runtime and compiled server from build stage
|
||||
# Copy Dart AOT runtime from build stage
|
||||
COPY --from=backend-build /runtime/ /
|
||||
COPY --from=backend-build /app/bin/server /app/bin/
|
||||
COPY --from=backend-build /app/web/build /app/web/build
|
||||
|
||||
# Copy the compiled Dart server
|
||||
COPY --from=backend-build /app/bin/server /app/bin/server
|
||||
|
||||
# Copy the built frontend (Node.js SSR app)
|
||||
COPY --from=frontend-build /app/web/build /app/web/build
|
||||
|
||||
# Create data and cache directories
|
||||
RUN mkdir -p /app/data /app/cache
|
||||
|
||||
# Copy startup script
|
||||
COPY docker/entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
# Set environment variables (can be overridden at runtime)
|
||||
ENV PORT=8080 \
|
||||
HOST=0.0.0.0 \
|
||||
DATA_DIR=/app/data \
|
||||
CACHE_DIR=/app/cache
|
||||
CACHE_DIR=/app/cache \
|
||||
BACKEND_URL=http://127.0.0.1:8081 \
|
||||
NODE_ENV=production
|
||||
|
||||
# Expose port
|
||||
# Expose port (frontend serves on this port)
|
||||
EXPOSE 8080
|
||||
|
||||
# Start server
|
||||
CMD ["/app/bin/server"]
|
||||
# Start both services using entrypoint script
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
49
README.md
49
README.md
@@ -21,6 +21,39 @@
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ 架构说明
|
||||
|
||||
Docker 部署采用**双服务架构**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Docker Container │
|
||||
│ │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ Node.js (SvelteKit SSR) │ │
|
||||
│ │ 端口: 8080 (对外暴露) │ │
|
||||
│ │ - 处理页面请求 │ │
|
||||
│ │ - 代理 /api/* → Dart 后端 │ │
|
||||
│ └──────────────┬───────────────┘ │
|
||||
│ │ 内部通信 │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ Dart Server (Shelf) │ │
|
||||
│ │ 端口: 8081 (仅内部) │ │
|
||||
│ │ - 提供 RESTful API │ │
|
||||
│ │ - 图片/缩略图服务 │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**工作流程:**
|
||||
1. 用户访问 `:8080`,请求由 SvelteKit SSR 处理
|
||||
2. 前端 `/api/*` 请求被代理到 Dart 后端 `:8081`
|
||||
3. Dart 后端处理数据请求,返回 JSON 或图片流
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 项目结构
|
||||
|
||||
```
|
||||
@@ -109,9 +142,13 @@ docker run -d \
|
||||
-p 8080:8080 \
|
||||
-v $(pwd)/data:/app/data \
|
||||
-v $(pwd)/cache:/app/cache \
|
||||
-e ORIGIN=http://localhost:8080 \
|
||||
-e BETTER_AUTH_SECRET=your-secret-key-here \
|
||||
loongyan
|
||||
```
|
||||
|
||||
> **注意**: 生产环境请务必设置 `ORIGIN` 为你的实际域名,并使用安全的随机字符串作为 `BETTER_AUTH_SECRET`。
|
||||
|
||||
### Docker Compose(推荐)
|
||||
|
||||
```yaml
|
||||
@@ -126,6 +163,8 @@ services:
|
||||
- ./cache:/app/cache
|
||||
environment:
|
||||
- PORT=8080
|
||||
- ORIGIN=http://localhost:8080
|
||||
- BETTER_AUTH_SECRET=your-secret-key-here
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
@@ -262,7 +301,9 @@ pnpm auth:schema
|
||||
|
||||
| 变量名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `PORT` | `8080` | 服务器监听端口 |
|
||||
| `PORT` | `8081` | Dart 后端监听端口(容器内部) |
|
||||
| `DATA_DIR` | `/app/data` | 数据目录 |
|
||||
| `CACHE_DIR` | `/app/cache` | 缓存目录 |
|
||||
|
||||
### 前端环境变量
|
||||
|
||||
@@ -270,9 +311,13 @@ pnpm auth:schema
|
||||
|
||||
```bash
|
||||
# web/.env
|
||||
PUBLIC_API_URL=http://localhost:8080/api/v1
|
||||
ORIGIN=http://localhost:8080 # 生产环境的实际域名
|
||||
BETTER_AUTH_SECRET=your-secret-key # 认证密钥(32字符随机字符串)
|
||||
BACKEND_URL=http://127.0.0.1:8081 # Dart 后端地址(内部通信)
|
||||
```
|
||||
|
||||
> **重要**: 生产环境必须设置 `ORIGIN` 和 `BETTER_AUTH_SECRET`。
|
||||
|
||||
---
|
||||
|
||||
## 📦 技术栈
|
||||
|
||||
30
docker/entrypoint.sh
Normal file
30
docker/entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# 启动 Dart 后端 (内部端口 8081)
|
||||
echo "Starting Dart backend on port 8081..."
|
||||
export PORT=8081
|
||||
/app/bin/server &
|
||||
BACKEND_PID=$!
|
||||
|
||||
# 等待后端启动
|
||||
sleep 2
|
||||
|
||||
# 启动前端 (Node.js SSR, 端口 8080)
|
||||
echo "Starting frontend on port 8080..."
|
||||
export PORT=8080
|
||||
export HOST=0.0.0.0
|
||||
export BACKEND_URL=http://127.0.0.1:8081
|
||||
cd /app/web/build
|
||||
node index.js &
|
||||
FRONTEND_PID=$!
|
||||
|
||||
# 捕获退出信号,优雅关闭两个服务
|
||||
trap "echo 'Shutting down...'; kill $BACKEND_PID $FRONTEND_PID 2>/dev/null; exit 0" SIGTERM SIGINT
|
||||
|
||||
# 等待任意进程退出
|
||||
wait -n $BACKEND_PID $FRONTEND_PID
|
||||
|
||||
# 如果有一个进程退出,杀死另一个
|
||||
kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true
|
||||
exit 1
|
||||
@@ -7,3 +7,7 @@ ORIGIN=""
|
||||
# For production use 32 characters and generated with high entropy
|
||||
# https://www.better-auth.com/docs/installation
|
||||
BETTER_AUTH_SECRET=""
|
||||
|
||||
# Backend API URL (Dart server)
|
||||
# In production, the frontend proxies /api requests to this URL
|
||||
BACKEND_URL="http://127.0.0.1:8080"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@inlang/paraglide-js": "^2.10.0",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { auth } from '$lib/server/auth';
|
||||
import { svelteKitHandler } from 'better-auth/svelte-kit';
|
||||
import { getTextDirection } from '$lib/paraglide/runtime';
|
||||
import { paraglideMiddleware } from '$lib/paraglide/server';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
const handleParaglide = ({ event, resolve }) =>
|
||||
@@ -32,6 +33,35 @@ const handleBetterAuth = async ({ event, resolve }) => {
|
||||
return svelteKitHandler({ event, resolve, auth, building });
|
||||
};
|
||||
|
||||
/**
|
||||
* API 代理处理 - 将 /api/v1 请求代理到 Dart 后端
|
||||
* 仅在生产环境生效,开发环境由 Vite proxy 处理
|
||||
* @type {import('@sveltejs/kit').Handle} */
|
||||
const handleApiProxy = async ({ event, resolve }) => {
|
||||
const { url, request } = event;
|
||||
|
||||
// 只处理 /api/v1 路径的请求
|
||||
if (url.pathname.startsWith('/api/v1')) {
|
||||
const backendUrl = env.BACKEND_URL ?? 'http://127.0.0.1:8080';
|
||||
const targetUrl = `${backendUrl}${url.pathname}${url.search}`;
|
||||
|
||||
// 转发请求到 Dart 后端
|
||||
const backendResponse = await fetch(targetUrl, {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.arrayBuffer() : undefined
|
||||
});
|
||||
|
||||
// 返回后端响应
|
||||
return new Response(backendResponse.body, {
|
||||
status: backendResponse.status,
|
||||
headers: backendResponse.headers
|
||||
});
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
const handleCache = async ({ event, resolve }) => {
|
||||
const response = await resolve(event);
|
||||
@@ -66,4 +96,4 @@ const handleCache = async ({ event, resolve }) => {
|
||||
return newResponse;
|
||||
};
|
||||
|
||||
export const handle = sequence(handleParaglide, handleBetterAuth, handleCache);
|
||||
export const handle = sequence(handleApiProxy, handleParaglide, handleBetterAuth, handleCache);
|
||||
@@ -1,13 +1,13 @@
|
||||
import { mdsvex } from 'mdsvex';
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
adapter: adapter({
|
||||
// 前端服务端口,通过环境变量配置
|
||||
env: { port: process.env.PORT ?? '3000', host: process.env.HOST ?? '0.0.0.0' }
|
||||
})
|
||||
},
|
||||
vitePlugin: {
|
||||
dynamicCompileOptions: ({ filename }) => ({ runes: !filename.includes('node_modules') })
|
||||
|
||||
Reference in New Issue
Block a user