feat: 实现 AIO 容器部署
This commit is contained in:
@@ -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"]
|
||||
@@ -0,0 +1,13 @@
|
||||
.git
|
||||
.github
|
||||
.local
|
||||
.vscode
|
||||
coverage
|
||||
dist
|
||||
node_modules
|
||||
output
|
||||
playwright-report
|
||||
test-results
|
||||
tmp
|
||||
*.log
|
||||
*.tsbuildinfo
|
||||
@@ -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
|
||||
及系统依赖,不依赖版本可能滞后的预制浏览器镜像标签。
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user