feat: 实现 AIO 容器部署

This commit is contained in:
SkyJourney
2026-07-26 16:55:36 +08:00
parent d1b5880a5b
commit 15dd2acfc0
13 changed files with 425 additions and 32 deletions
+77
View File
@@ -0,0 +1,77 @@
# syntax=docker/dockerfile:1
FROM node:22-bookworm-slim AS build
WORKDIR /app
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
COPY package.json package-lock.json ./
COPY apps/server/package.json apps/server/package.json
COPY apps/web/package.json apps/web/package.json
COPY packages/core/package.json packages/core/package.json
COPY packages/renderer/package.json packages/renderer/package.json
RUN npm ci
COPY tsconfig.base.json ./
COPY apps ./apps
COPY packages ./packages
RUN npm run build
FROM node:22-bookworm-slim AS production-dependencies
WORKDIR /app
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
COPY package.json package-lock.json ./
COPY apps/server/package.json apps/server/package.json
COPY apps/web/package.json apps/web/package.json
COPY packages/core/package.json packages/core/package.json
COPY packages/renderer/package.json packages/renderer/package.json
RUN npm ci --omit=dev --workspace @md-to-pdf/server --include-workspace-root
FROM node:22-bookworm-slim AS runtime
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
nginx \
fonts-noto-cjk \
fonts-noto-color-emoji \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system pwuser \
&& useradd --system --create-home --gid pwuser --shell /bin/bash pwuser
WORKDIR /app
COPY --chown=pwuser:pwuser package.json package-lock.json ./
COPY --chown=pwuser:pwuser apps/server/package.json apps/server/package.json
COPY --chown=pwuser:pwuser apps/web/package.json apps/web/package.json
COPY --chown=pwuser:pwuser packages/core/package.json packages/core/package.json
COPY --chown=pwuser:pwuser packages/renderer/package.json packages/renderer/package.json
COPY --chown=pwuser:pwuser --from=production-dependencies /app/node_modules ./node_modules
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN npx playwright install --with-deps chromium \
&& chown -R pwuser:pwuser /ms-playwright
COPY --chown=pwuser:pwuser --from=build /app/apps/server/dist ./apps/server/dist
COPY --chown=pwuser:pwuser --from=build /app/apps/web/dist ./apps/web/dist
COPY --chown=pwuser:pwuser --from=build /app/packages/core/dist ./packages/core/dist
COPY --chown=pwuser:pwuser --from=build /app/packages/renderer/dist ./packages/renderer/dist
COPY --chown=pwuser:pwuser themes ./themes
COPY deploy/nginx.conf /etc/nginx/nginx.conf
COPY --chown=pwuser:pwuser deploy/entrypoint.sh /app/deploy/entrypoint.sh
RUN chmod 0755 /app/deploy/entrypoint.sh
ENV HOST=127.0.0.1 \
PORT=3001 \
PDF_RENDER_ORIGIN=http://127.0.0.1:8080 \
PDF_CONCURRENCY=1 \
PDF_MAX_QUEUE=4 \
PDF_TIMEOUT_MS=120000
USER pwuser
EXPOSE 8080
ENTRYPOINT ["/app/deploy/entrypoint.sh"]
+13
View File
@@ -0,0 +1,13 @@
.git
.github
.local
.vscode
coverage
dist
node_modules
output
playwright-report
test-results
tmp
*.log
*.tsbuildinfo
+76
View File
@@ -0,0 +1,76 @@
# AIO 容器部署
本目录提供单容器部署:Nginx 在 `8080` 端口提供前端,并将
`/api/` 代理到同容器内的 Fastify;Fastify 复用固定版本
Playwright Chromium 生成 PDF。
## 启动
要求已经安装并启动 Docker Desktop。
```powershell
docker compose -f deploy\compose.yaml up --build -d
docker compose -f deploy\compose.yaml ps
```
打开:
```text
http://localhost:8080
```
健康检查:
```powershell
Invoke-RestMethod -Uri 'http://localhost:8080/api/health'
```
查看日志和停止:
```powershell
docker compose -f deploy\compose.yaml logs -f
docker compose -f deploy\compose.yaml down
```
## 构建发布标签
本地验证完成后,可以为镜像增加发布标签:
```powershell
docker tag md-to-pdf:local yixiong/md-to-pdf:v0.1.0
```
该命令只创建本地标签,不会自动推送到镜像仓库。
## 主题挂载
Compose 默认将项目的 `.local/themes` 只读挂载到容器中的
`/app/.local/themes`。可以通过环境变量指定其他目录:
```powershell
$env:MD_TO_PDF_THEME_DIR = 'D:\md-to-pdf-themes'
docker compose -f deploy\compose.yaml up -d
```
目录下每套主题都应包含 `theme.json`。主题规范详见
[`docs/THEMES.md`](../docs/THEMES.md)。
本机通过导入工具生成的 Typora 主题不会复制进镜像;只有显式挂载后,
容器才会读取这些本机文件。
## 运行参数
| 环境变量 | 默认值 | 说明 |
| --- | ---: | --- |
| `MD_TO_PDF_PORT` | `8080` | 宿主机监听端口 |
| `MD_TO_PDF_THEME_DIR` | `../.local/themes` | 宿主机主题目录 |
| `PDF_CONCURRENCY` | `1` | 同时执行的 PDF 任务数 |
| `PDF_MAX_QUEUE` | `4` | 等待队列上限 |
| `PDF_TIMEOUT_MS` | `120000` | 单次 PDF 生成超时 |
增加 PDF 并发会显著增加 Chromium 内存占用。单机部署建议先保持默认值,
再根据文档复杂度和可用内存调整。
容器为 Chromium 提供 1 GiB 共享内存,并以非 root `pwuser` 运行。
构建阶段使用项目锁定的 Playwright `1.62.0` 安装精确匹配的 Chromium
及系统依赖,不依赖版本可能滞后的预制浏览器镜像标签。
+35
View File
@@ -0,0 +1,35 @@
name: md-to-pdf
services:
app:
build:
context: ..
dockerfile: deploy/Dockerfile
image: md-to-pdf:local
init: true
ports:
- "${MD_TO_PDF_PORT:-8080}:8080"
environment:
HOST: 127.0.0.1
PORT: 3001
PDF_RENDER_ORIGIN: http://127.0.0.1:8080
PDF_CONCURRENCY: "${PDF_CONCURRENCY:-1}"
PDF_MAX_QUEUE: "${PDF_MAX_QUEUE:-4}"
PDF_TIMEOUT_MS: "${PDF_TIMEOUT_MS:-120000}"
volumes:
- "${MD_TO_PDF_THEME_DIR:-../.local/themes}:/app/.local/themes:ro"
shm_size: "1gb"
security_opt:
- no-new-privileges:true
restart: unless-stopped
stop_grace_period: 30s
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:8080/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 10s
timeout: 3s
retries: 6
start_period: 30s
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -Eeuo pipefail
server_pid=""
nginx_pid=""
terminate() {
trap - TERM INT
if [[ -n "${nginx_pid}" ]]; then
kill -TERM "${nginx_pid}" 2>/dev/null || true
fi
if [[ -n "${server_pid}" ]]; then
kill -TERM "${server_pid}" 2>/dev/null || true
fi
}
trap terminate TERM INT
node /app/apps/server/dist/index.js &
server_pid=$!
nginx -g "daemon off;" &
nginx_pid=$!
set +e
wait -n "${server_pid}" "${nginx_pid}"
status=$?
set -e
terminate
wait "${server_pid}" 2>/dev/null || true
wait "${nginx_pid}" 2>/dev/null || true
exit "${status}"
+50
View File
@@ -0,0 +1,50 @@
worker_processes auto;
pid /tmp/nginx.pid;
error_log /dev/stderr warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
types {
application/javascript mjs;
}
default_type application/octet-stream;
access_log /dev/stdout;
sendfile on;
keepalive_timeout 65;
client_max_body_size 3m;
client_body_temp_path /tmp/nginx-client-body;
proxy_temp_path /tmp/nginx-proxy;
fastcgi_temp_path /tmp/nginx-fastcgi;
uwsgi_temp_path /tmp/nginx-uwsgi;
scgi_temp_path /tmp/nginx-scgi;
server {
listen 8080;
server_name _;
root /app/apps/web/dist;
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 130s;
proxy_send_timeout 130s;
proxy_buffering off;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}