更新dockerfile
This commit is contained in:
35
Dockerfile
35
Dockerfile
@@ -15,7 +15,7 @@ RUN pnpm install --frozen-lockfile
|
|||||||
# Copy frontend source
|
# Copy frontend source
|
||||||
COPY web/ ./
|
COPY web/ ./
|
||||||
|
|
||||||
# Build frontend
|
# Build frontend (SSR with adapter-node)
|
||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
# Stage 2: Build backend
|
# Stage 2: Build backend
|
||||||
@@ -29,14 +29,12 @@ RUN dart pub get
|
|||||||
|
|
||||||
# Copy app source code and compile
|
# Copy app source code and compile
|
||||||
COPY bin/ ./bin/
|
COPY bin/ ./bin/
|
||||||
COPY --from=frontend-build /app/web/build ./web/build/
|
|
||||||
|
|
||||||
RUN dart compile exe bin/server.dart -o bin/server
|
RUN dart compile exe bin/server.dart -o bin/server
|
||||||
|
|
||||||
# Stage 3: Runtime image with libvips
|
# Stage 3: Runtime image with Node.js, libvips and Dart runtime
|
||||||
FROM debian:bookworm-slim AS 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 \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libvips42 \
|
libvips42 \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@@ -45,21 +43,32 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
WORKDIR /app
|
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 /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
|
# Create data and cache directories
|
||||||
RUN mkdir -p /app/data /app/cache
|
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)
|
# Set environment variables (can be overridden at runtime)
|
||||||
ENV PORT=8080 \
|
ENV PORT=8080 \
|
||||||
|
HOST=0.0.0.0 \
|
||||||
DATA_DIR=/app/data \
|
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
|
EXPOSE 8080
|
||||||
|
|
||||||
# Start server
|
# Start both services using entrypoint script
|
||||||
CMD ["/app/bin/server"]
|
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 \
|
-p 8080:8080 \
|
||||||
-v $(pwd)/data:/app/data \
|
-v $(pwd)/data:/app/data \
|
||||||
-v $(pwd)/cache:/app/cache \
|
-v $(pwd)/cache:/app/cache \
|
||||||
|
-e ORIGIN=http://localhost:8080 \
|
||||||
|
-e BETTER_AUTH_SECRET=your-secret-key-here \
|
||||||
loongyan
|
loongyan
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **注意**: 生产环境请务必设置 `ORIGIN` 为你的实际域名,并使用安全的随机字符串作为 `BETTER_AUTH_SECRET`。
|
||||||
|
|
||||||
### Docker Compose(推荐)
|
### Docker Compose(推荐)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -126,6 +163,8 @@ services:
|
|||||||
- ./cache:/app/cache
|
- ./cache:/app/cache
|
||||||
environment:
|
environment:
|
||||||
- PORT=8080
|
- PORT=8080
|
||||||
|
- ORIGIN=http://localhost:8080
|
||||||
|
- BETTER_AUTH_SECRET=your-secret-key-here
|
||||||
restart: unless-stopped
|
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
|
```bash
|
||||||
# web/.env
|
# 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
|
# For production use 32 characters and generated with high entropy
|
||||||
# https://www.better-auth.com/docs/installation
|
# https://www.better-auth.com/docs/installation
|
||||||
BETTER_AUTH_SECRET=""
|
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/compat": "^2.0.2",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@inlang/paraglide-js": "^2.10.0",
|
"@inlang/paraglide-js": "^2.10.0",
|
||||||
"@sveltejs/adapter-auto": "^7.0.0",
|
"@sveltejs/adapter-node": "^5.2.12",
|
||||||
"@sveltejs/kit": "^2.50.2",
|
"@sveltejs/kit": "^2.50.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { auth } from '$lib/server/auth';
|
|||||||
import { svelteKitHandler } from 'better-auth/svelte-kit';
|
import { svelteKitHandler } from 'better-auth/svelte-kit';
|
||||||
import { getTextDirection } from '$lib/paraglide/runtime';
|
import { getTextDirection } from '$lib/paraglide/runtime';
|
||||||
import { paraglideMiddleware } from '$lib/paraglide/server';
|
import { paraglideMiddleware } from '$lib/paraglide/server';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Handle} */
|
/** @type {import('@sveltejs/kit').Handle} */
|
||||||
const handleParaglide = ({ event, resolve }) =>
|
const handleParaglide = ({ event, resolve }) =>
|
||||||
@@ -32,6 +33,35 @@ const handleBetterAuth = async ({ event, resolve }) => {
|
|||||||
return svelteKitHandler({ event, resolve, auth, building });
|
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} */
|
/** @type {import('@sveltejs/kit').Handle} */
|
||||||
const handleCache = async ({ event, resolve }) => {
|
const handleCache = async ({ event, resolve }) => {
|
||||||
const response = await resolve(event);
|
const response = await resolve(event);
|
||||||
@@ -66,4 +96,4 @@ const handleCache = async ({ event, resolve }) => {
|
|||||||
return newResponse;
|
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 { mdsvex } from 'mdsvex';
|
||||||
import adapter from '@sveltejs/adapter-auto';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
kit: {
|
kit: {
|
||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
adapter: adapter({
|
||||||
// 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.
|
env: { port: process.env.PORT ?? '3000', host: process.env.HOST ?? '0.0.0.0' }
|
||||||
adapter: adapter()
|
})
|
||||||
},
|
},
|
||||||
vitePlugin: {
|
vitePlugin: {
|
||||||
dynamicCompileOptions: ({ filename }) => ({ runes: !filename.includes('node_modules') })
|
dynamicCompileOptions: ({ filename }) => ({ runes: !filename.includes('node_modules') })
|
||||||
@@ -16,4 +16,4 @@ const config = {
|
|||||||
extensions: ['.svelte', '.svx']
|
extensions: ['.svelte', '.svx']
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
Reference in New Issue
Block a user