diff --git a/README.md b/README.md index 3761bf3..b2eff46 100644 --- a/README.md +++ b/README.md @@ -44,32 +44,37 @@ npm run check:shadcn ## Docker 部署 -默认单容器部署: +推荐单容器部署。服务器只需要 `deploy/docker-compose.yml` 和 `.env`,不需要源码构建: ```bash cd deploy cp .env.example .env -docker compose up -d --build +# 修改 .env 里的域名和管理员密码 +docker compose pull +docker compose up -d ``` 这个容器内部集成:API、Web、Nginx、Postfix、Dovecot、OpenDKIM。 -也可以直接使用 GitHub Actions 自动发布到 GHCR 的镜像: +如果 GHCR 镜像是私有的,先登录: + +```bash +echo "" | docker login ghcr.io -u --password-stdin +``` + +本地源码构建: ```bash cd deploy cp .env.example .env -# 修改 .env 里的域名、管理员密码,确认 LANQIN_IMAGE -# LANQIN_IMAGE=ghcr.io/lanqin996/lanqin-email:latest -docker compose pull -docker compose up -d +docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build ``` 如需多容器调试版: ```bash cd deploy -docker compose -f docker-compose.stack.yml up -d --build +docker compose -f docker-compose.stack.yml -f docker-compose.stack.build.yml up -d --build ``` 真实公网收发前需要正确配置 MX/SPF/DKIM/DMARC,并确认云厂商开放 25/587/993 端口。 diff --git a/deploy/.env.example b/deploy/.env.example index 3e4e4bc..c5dff6c 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -6,7 +6,7 @@ # Docker 镜像 # ========================= # 单容器部署镜像。默认使用你的 GHCR 镜像。 -# 如果只想本机构建,可以临时改成 lanqin-email:local 后执行 docker compose up -d --build。 +# 本地源码构建请用:docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build LANQIN_IMAGE=ghcr.io/lanqin996/lanqin-email:latest # 多容器调试版镜像。只在 docker-compose.stack.yml 中使用。 diff --git a/deploy/README.md b/deploy/README.md index 22b0e4d..7d2b00d 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,14 +1,26 @@ # LanQin Email Docker 部署说明 -## 默认:单容器部署 +## 最简单部署:单容器镜像版 + +服务器上不需要源码构建,只要 `docker-compose.yml` 和 `.env` 即可。 ```bash cd deploy cp .env.example .env -# 修改 LANQIN_PUBLIC_HOSTNAME / LANQIN_ADMIN_EMAIL / LANQIN_ADMIN_PASSWORD -docker compose up -d --build +# 修改 LANQIN_PUBLIC_HOSTNAME / LANQIN_PUBLIC_BASE_URL / LANQIN_ADMIN_EMAIL / LANQIN_ADMIN_PASSWORD +docker compose pull +docker compose up -d ``` +也可以使用脚本: + +```bash +cd deploy +bash install.sh +``` + +第一次执行会生成 `.env` 并提示你修改配置;修改完成后再次执行 `bash install.sh`。 + 默认只启动一个业务容器: ```text @@ -24,21 +36,23 @@ lanqin-email - Dovecot - OpenDKIM -常用日志: +常用命令: ```bash +# 查看日志 docker compose logs -f lanqin-email + +# 更新镜像并重启 +docker compose pull +docker compose up -d + +# 停止 +docker compose down ``` -## 使用 GitHub Actions 构建好的镜像 +## GHCR 镜像权限 -仓库已添加 `.github/workflows/docker.yml`: - -- pull request:检查前端 shadcn 规则、前端构建、后端测试。 -- push 到 `main` / `master`:检查通过后发布 Docker 镜像到 GHCR。 -- push tag,例如 `v1.0.0`:发布对应 tag 镜像。 - -默认发布镜像: +默认镜像: ```text ghcr.io/lanqin996/lanqin-email:latest @@ -49,33 +63,49 @@ ghcr.io/lanqin996/lanqin-email-dovecot:latest ghcr.io/lanqin996/lanqin-email-opendkim:latest ``` -单容器部署时,确认 `.env` 里的 `LANQIN_IMAGE` 是你的 GHCR 镜像: +如果拉取时报: -```env -LANQIN_IMAGE=ghcr.io/lanqin996/lanqin-email:latest +```text +unauthorized ``` -然后在服务器执行: +说明 GHCR Package 还是私有,二选一: -```bash -cd deploy -docker compose pull -docker compose up -d -``` - -如果镜像是私有的,服务器需要先登录 GHCR: +1. 到 GitHub Packages 把镜像改成 Public。 +2. 在服务器登录 GHCR: ```bash echo "" | docker login ghcr.io -u --password-stdin ``` -## 可选:多容器调试部署 +## 本地源码构建 -如果需要分别查看 Postfix / Dovecot / OpenDKIM 日志,可以使用保留的 stack 编排: +如果你是在完整源码仓库里本机构建,使用 build override: ```bash cd deploy -docker compose -f docker-compose.stack.yml up -d --build +cp .env.example .env +docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build +``` + +这样会使用 `deploy/all-in-one/Dockerfile` 构建单容器镜像。 + +## 可选:多容器调试部署 + +如果需要分别查看 Postfix / Dovecot / OpenDKIM 日志,可以使用 stack 编排。 + +拉取镜像版: + +```bash +cd deploy +docker compose -f docker-compose.stack.yml up -d +``` + +源码构建版: + +```bash +cd deploy +docker compose -f docker-compose.stack.yml -f docker-compose.stack.build.yml up -d --build ``` ## DNS @@ -102,4 +132,3 @@ docker compose -f docker-compose.stack.yml up -d --build - 建议在服务器或边缘网关配置 HTTPS。 - 云厂商通常默认封禁 25 端口,需要单独申请解封。 - SQLite 适合 V1 单机部署;多节点部署前迁移到 PostgreSQL,并把 Postfix/Dovecot maps 改为 PostgreSQL。 - diff --git a/deploy/docker-compose.build.yml b/deploy/docker-compose.build.yml new file mode 100644 index 0000000..0fbcd74 --- /dev/null +++ b/deploy/docker-compose.build.yml @@ -0,0 +1,5 @@ +services: + lanqin-email: + build: + context: .. + dockerfile: deploy/all-in-one/Dockerfile diff --git a/deploy/docker-compose.stack.build.yml b/deploy/docker-compose.stack.build.yml new file mode 100644 index 0000000..18171f6 --- /dev/null +++ b/deploy/docker-compose.stack.build.yml @@ -0,0 +1,22 @@ +services: + api: + build: + context: .. + dockerfile: deploy/api.Dockerfile + + web: + build: + context: .. + dockerfile: deploy/web.Dockerfile + + postfix: + build: + context: ./postfix + + dovecot: + build: + context: ./dovecot + + opendkim: + build: + context: ./opendkim diff --git a/deploy/docker-compose.stack.yml b/deploy/docker-compose.stack.yml index a144233..954ce13 100644 --- a/deploy/docker-compose.stack.yml +++ b/deploy/docker-compose.stack.yml @@ -1,11 +1,6 @@ -version: "3.9" - services: api: - image: ${LANQIN_API_IMAGE:-lanqin-email-api:local} - build: - context: .. - dockerfile: deploy/api.Dockerfile + image: ${LANQIN_API_IMAGE:-ghcr.io/lanqin996/lanqin-email-api:latest} env_file: .env volumes: - lanqin-data:/data @@ -16,10 +11,7 @@ services: restart: unless-stopped web: - image: ${LANQIN_WEB_IMAGE:-lanqin-email-web:local} - build: - context: .. - dockerfile: deploy/web.Dockerfile + image: ${LANQIN_WEB_IMAGE:-ghcr.io/lanqin996/lanqin-email-web:latest} restart: unless-stopped nginx: @@ -35,9 +27,7 @@ services: restart: unless-stopped postfix: - image: ${LANQIN_POSTFIX_IMAGE:-lanqin-email-postfix:local} - build: - context: ./postfix + image: ${LANQIN_POSTFIX_IMAGE:-ghcr.io/lanqin996/lanqin-email-postfix:latest} env_file: .env volumes: - lanqin-data:/data @@ -51,9 +41,7 @@ services: restart: unless-stopped dovecot: - image: ${LANQIN_DOVECOT_IMAGE:-lanqin-email-dovecot:local} - build: - context: ./dovecot + image: ${LANQIN_DOVECOT_IMAGE:-ghcr.io/lanqin996/lanqin-email-dovecot:latest} env_file: .env volumes: - lanqin-data:/data @@ -63,9 +51,7 @@ services: restart: unless-stopped opendkim: - image: ${LANQIN_OPENDKIM_IMAGE:-lanqin-email-opendkim:local} - build: - context: ./opendkim + image: ${LANQIN_OPENDKIM_IMAGE:-ghcr.io/lanqin996/lanqin-email-opendkim:latest} env_file: .env volumes: - lanqin-data:/data:ro diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 03e5efa..2bca6f3 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -1,11 +1,6 @@ -version: "3.9" - services: lanqin-email: - image: ${LANQIN_IMAGE:-lanqin-email:local} - build: - context: .. - dockerfile: deploy/all-in-one/Dockerfile + image: ${LANQIN_IMAGE:-ghcr.io/lanqin996/lanqin-email:latest} env_file: .env ports: - "80:80" diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100644 index 0000000..8aa0d24 --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +if ! command -v docker >/dev/null 2>&1; then + echo "未检测到 docker,请先安装 Docker Engine / Docker Compose。" >&2 + exit 1 +fi + +if ! docker compose version >/dev/null 2>&1; then + echo "未检测到 docker compose,请先安装 Docker Compose v2。" >&2 + exit 1 +fi + +if [ ! -f .env ]; then + cp .env.example .env + echo "已生成 deploy/.env。请先编辑域名和管理员密码:" + echo " LANQIN_PUBLIC_HOSTNAME" + echo " LANQIN_PUBLIC_BASE_URL" + echo " LANQIN_ADMIN_EMAIL" + echo " LANQIN_ADMIN_PASSWORD" + echo + echo "编辑完成后再次执行:./install.sh" + exit 0 +fi + +echo "拉取镜像..." +docker compose pull + +echo "启动服务..." +docker compose up -d + +echo "完成。查看日志:docker compose logs -f lanqin-email"