更新dockerfile

This commit is contained in:
2026-03-24 22:53:03 +08:00
parent d09a0487ae
commit a2c835049f
7 changed files with 141 additions and 23 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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') })