diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..d38aa90
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,21 @@
+.git
+.github
+.husky
+.vscode
+.idea
+
+node_modules
+dist
+dev
+coverage
+.nyc_output
+
+docs
+*.log
+npm-debug.log*
+
+.specstory
+.spec-workflow
+.serena
+.claude
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..eaae827
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,37 @@
+# syntax=docker/dockerfile:1.7
+
+FROM node:22-alpine AS builder
+
+WORKDIR /app
+
+ENV HUSKY=0
+
+COPY package.json package-lock.json ./
+RUN npm ci
+
+COPY . .
+
+ARG MENAV_ENABLE_SYNC=false
+ARG MENAV_IMPORT_BOOKMARKS=false
+
+RUN if [ "${MENAV_IMPORT_BOOKMARKS}" = "true" ]; then \
+ MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks; \
+ fi \
+ && if [ "${MENAV_ENABLE_SYNC}" = "true" ]; then \
+ npm run build; \
+ else \
+ PROJECTS_ENABLED=false HEATMAP_ENABLED=false RSS_ENABLED=false npm run build; \
+ fi
+
+FROM nginx:1.27-alpine AS runtime
+
+WORKDIR /usr/share/nginx/html
+
+COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
+COPY --from=builder /app/dist ./
+
+EXPOSE 80
+
+STOPSIGNAL SIGQUIT
+
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/README.md b/README.md
index 458f6d9..eabbc44 100644
--- a/README.md
+++ b/README.md
@@ -453,6 +453,52 @@ npm run format
+### Docker 部署(推荐)
+
+
+点击展开
+
+MeNav 构建后是纯静态站点(`dist/`),仓库已内置 Docker 部署文件,可直接构建并运行。
+
+#### 方式一:Docker Compose(推荐)
+
+1. 准备配置(首次使用):
+ - 按 [设置配置文件](#设置配置文件) 完成 `config/user/` 配置
+ - 如果要导入书签,可先把书签 HTML 放到 `bookmarks/` 目录
+
+2. 构建并启动:
+
+```bash
+docker compose up -d --build
+```
+
+默认访问地址:`http://localhost:8080`
+
+3. 常用可选参数(通过环境变量传入):
+
+```bash
+MENAV_PORT=80 MENAV_ENABLE_SYNC=true MENAV_IMPORT_BOOKMARKS=true docker compose up -d --build
+```
+
+- `MENAV_PORT`:宿主机端口(默认 `8080`)
+- `MENAV_ENABLE_SYNC`:是否在镜像构建时联网执行 `sync-*`(默认 `false`,更稳定)
+- `MENAV_IMPORT_BOOKMARKS`:是否在镜像构建时执行 `npm run import-bookmarks`(默认 `false`)
+
+4. 更新站点:
+ - 修改配置或内容后,重新执行 `docker compose up -d --build`
+
+#### 方式二:直接使用 Docker 命令
+
+```bash
+docker build -t menav \
+ --build-arg MENAV_ENABLE_SYNC=false \
+ --build-arg MENAV_IMPORT_BOOKMARKS=false .
+
+docker run -d --name menav -p 8080:80 --restart unless-stopped menav
+```
+
+
+
### 部署到服务器
@@ -483,7 +529,7 @@ server {
index index.html;
location / {
- try_files $uri $uri/ /index.html;
+ try_files $uri $uri/ /404.html;
}
}
```
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5e151c2
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+services:
+ menav:
+ container_name: menav
+ image: menav:latest
+ build:
+ context: .
+ dockerfile: Dockerfile
+ args:
+ MENAV_ENABLE_SYNC: ${MENAV_ENABLE_SYNC:-false}
+ MENAV_IMPORT_BOOKMARKS: ${MENAV_IMPORT_BOOKMARKS:-false}
+ ports:
+ - '${MENAV_PORT:-8080}:80'
+ restart: unless-stopped
diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf
new file mode 100644
index 0000000..c8e12e7
--- /dev/null
+++ b/docker/nginx/default.conf
@@ -0,0 +1,30 @@
+server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ charset utf-8;
+
+ location = / {
+ try_files /index.html =404;
+ add_header Cache-Control "no-store";
+ }
+
+ location ~* \.(?:css|js|mjs|json|ico|png|jpe?g|gif|svg|webp|txt|xml|map)$ {
+ try_files $uri =404;
+ access_log off;
+ add_header Cache-Control "public, max-age=3600";
+ }
+
+ location ~* \.html$ {
+ try_files $uri =404;
+ add_header Cache-Control "no-store";
+ }
+
+ location / {
+ try_files $uri $uri/ /404.html;
+ add_header Cache-Control "no-store";
+ }
+}