# nano_project 本机一步步部署指南 这份文档适合第一次在本机把整个项目跑起来的人,目标是: - 在一台 `Linux` 或 `WSL2 Ubuntu` 机器上 - 用 `Docker` 跑完整链路 - 最后能在浏览器里注册账号,并自动创建你的专属实例 这套项目当前的推荐本机测试方式是: - `auth-portal` - `authz-service` - `deploy-control` - `router-proxy` - `app-instance` 全部一起跑。 如果你只单独跑某个前端页面,页面能打开,但注册、登录、创建实例这些核心能力不一定会通。 --- ## 0. 先说前提 ### 适合的环境 推荐: - Linux - WSL2 Ubuntu 不推荐直接按这份文档在纯 Windows 命令行里照抄,因为这里依赖: - Docker - Bash 脚本 - Docker Socket 挂载 - 宿主机目录挂载 ### 你需要先装好的工具 - `docker` - `git` - `curl` - `openssl` - `python3` 先检查: ```bash docker --version docker ps python3 --version openssl version curl --version ``` 如果 `docker ps` 报错,先把 Docker 启动起来。 --- ## 1. 进入项目根目录 ```bash cd /home/ivan/xuan/nano_project ``` 你执行完以后,建议顺手确认一下当前目录: ```bash pwd ``` 你应该看到: ```text /home/ivan/xuan/nano_project ``` --- ## 2. 准备一套本机测试变量 ### 为什么这里用 `127.0.0.1.nip.io` 因为这是最省事的本机测试域名方案。 它的作用是: - `alice.127.0.0.1.nip.io` - 自动解析到 `127.0.0.1` 这样 `router-proxy` 就能按子域名区分不同实例。 ### 直接复制执行 ```bash export PROJECT_ROOT=/home/ivan/xuan/nano_project export NANO_NET=nano-instance-edge export NANO_DEPLOY_TOKEN="$(openssl rand -hex 32)" export NANO_AUTHZ_INTERNAL_TOKEN="$(openssl rand -hex 32)" export NANO_SERVER_IP=127.0.0.1 export NANO_BASE_DOMAIN=127.0.0.1.nip.io export NANO_PROVIDER=openai export NANO_MODEL=openai/gpt-5 export NANO_API_KEY='把这里换成你自己的模型 API Key' export NANO_API_BASE='' export NANO_AUTHZ_URL='http://nano-authz-service:19090' export NANO_OUTLOOK_MCP_URL='' export NANO_OUTLOOK_MCP_SERVER_ID='outlook_mcp' export NANO_DEPLOY_URL='http://nano-deploy-control:8090' ``` ### 这里每个变量大概是干什么的 - `PROJECT_ROOT` - 仓库根目录 - `NANO_NET` - 所有容器共用的 Docker 网络 - `NANO_DEPLOY_TOKEN` - `auth-portal` / `authz-service` 调 `deploy-control` 时的鉴权 token - `NANO_AUTHZ_INTERNAL_TOKEN` - AuthZ 内部接口 token - `NANO_BASE_DOMAIN` - 实例基础域名 - `NANO_PROVIDER` - 新实例默认模型提供商 - `NANO_MODEL` - 新实例默认模型 - `NANO_API_KEY` - 新实例默认模型 API Key - `NANO_API_BASE` - 自定义模型网关地址,没有就留空 - `NANO_AUTHZ_URL` - 容器网络内访问 AuthZ 的地址 - `NANO_DEPLOY_URL` - 容器网络内访问 deploy-control 的地址 - `NANO_OUTLOOK_MCP_URL` - 可选;如果你有独立 Outlook MCP 服务,可以在这里填 - `NANO_OUTLOOK_MCP_SERVER_ID` - Outlook MCP 默认 server id,当前推荐固定 `outlook_mcp` ### 一个特别重要的提醒 `NANO_API_KEY` 不能空着。 如果这里不填,新用户注册时虽然页面可能能走到一半,但自动创建 `app-instance` 时大概率失败,因为实例配置里需要 `APP_INSTANCE_API_KEY`。 `NANO_AUTHZ_URL` 和 `NANO_DEPLOY_URL` 也不能留空,而且必须带协议头。 正确写法: ```text http://nano-authz-service:19090 http://nano-deploy-control:8090 ``` 错误写法: ```text nano-authz-service:19090 nano-deploy-control:8090 172.19.207.13:19090 172.19.207.13:8090 ``` 如果这里漏了 `http://`,注册页很容易直接报: ```text 502: Request URL is missing an 'http://' or 'https://' protocol. ``` 还有一个很容易忽略的点: - 你在 shell 里重新 `export NANO_DEPLOY_URL=...` - 不会自动修改已经在运行中的 `nano-authz-service` 和 `nano-auth-portal` 也就是说: - 变量改对了 - 但容器没重建 注册页还是会继续报同一个 502。 改完变量以后,至少要重建这些容器: - `nano-authz-service` - `nano-auth-portal` --- ## 3. 创建运行目录 ```bash mkdir -p \ "$PROJECT_ROOT/authz-service/runtime/data" \ "$PROJECT_ROOT/app-instance/runtime/instances" \ "$PROJECT_ROOT/app-instance/runtime/registry" \ "$PROJECT_ROOT/router-proxy/runtime/conf.d" ``` 这一步的作用是给下面几个东西留持久化空间: - AuthZ 数据 - 实例注册表 - 每个用户实例的配置目录 - router-proxy 生成出来的路由文件 --- ## 4. 构建镜像 第一次构建会比较久,正常情况要等几分钟。 ```bash cd "$PROJECT_ROOT" docker build -t nano/app-instance:latest app-instance docker build -t nano/authz-service:latest authz-service docker build -t nano/deploy-control:latest deploy-control docker build -t nano/auth-portal:latest auth-portal/src ``` 如果中间有某个镜像失败,不要继续往下跑,先把失败那一步修掉。 常见失败原因: - Docker 没启动 - 网络拉镜像失败 - 你的本机磁盘空间不够 --- ## 5. 创建共享 Docker 网络 ```bash docker network inspect "$NANO_NET" >/dev/null 2>&1 || docker network create "$NANO_NET" ``` 执行完后可确认: ```bash docker network ls | grep "$NANO_NET" ``` 应该能看到: ```text nano-instance-edge ``` --- ## 6. 启动统一入口代理 `router-proxy` ```bash cd "$PROJECT_ROOT" PROXY_NETWORK_NAME="$NANO_NET" \ PROXY_HTTP_PORT=8088 \ ./router-proxy/start-proxy.sh --replace ``` 启动后,统一入口走: ```text http://<你的实例slug>.127.0.0.1.nip.io:8088 ``` 例如: ```text http://alice.127.0.0.1.nip.io:8088 ``` --- ## 7. 启动 `authz-service` ```bash docker rm -f nano-authz-service >/dev/null 2>&1 || true docker run -d \ --name nano-authz-service \ --restart unless-stopped \ --network "$NANO_NET" \ -p 19090:19090 \ -v "$PROJECT_ROOT/authz-service/runtime/data:/var/lib/authz-service/data" \ -e AUTHZ_ISSUER="$NANO_AUTHZ_URL" \ -e AUTHZ_INTERNAL_TOKEN="$NANO_AUTHZ_INTERNAL_TOKEN" \ -e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \ -e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \ nano/authz-service:latest ``` ### 这里有个很关键的坑 `AUTHZ_ISSUER` 这里不能写成: ```text http://127.0.0.1:19090 ``` 因为后面新创建的 `app-instance` 也是通过 Docker 网络去访问 AuthZ 的。 所以这里要写容器网络里可访问的地址: ```text http://nano-authz-service:19090 ``` `DEPLOY_API_BASE_URL` 也一样,不能空,不能只写 `host:port`。 这里如果传成空字符串,或者写成: ```text nano-deploy-control:8090 ``` 注册页会在 `authz-service -> deploy-control` 这一步直接报: ```text 502: Request URL is missing an 'http://' or 'https://' protocol. ``` 启动完可以立刻确认: ```bash docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)=' ``` --- ## 8. 启动 `deploy-control` 这一步最容易配错的点是挂载目录。 一定要注意: - `app-instance` 和 `router-proxy` 的宿主机路径,要按原路径挂进容器 - 不能偷懒挂到容器里的另一个短路径比如 `/app-instance` - 同时要把 `APP_INSTANCE_DIR` 和 `ROUTER_PROXY_DIR` 也明确传进去 直接执行: ```bash docker rm -f nano-deploy-control >/dev/null 2>&1 || true docker run -d \ --name nano-deploy-control \ --restart unless-stopped \ --network "$NANO_NET" \ -p 8090:8090 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$PROJECT_ROOT/app-instance:$PROJECT_ROOT/app-instance" \ -v "$PROJECT_ROOT/router-proxy:$PROJECT_ROOT/router-proxy" \ -e APP_INSTANCE_DIR="$PROJECT_ROOT/app-instance" \ -e ROUTER_PROXY_DIR="$PROJECT_ROOT/router-proxy" \ -e DEPLOY_CONTROL_API_TOKEN="$NANO_DEPLOY_TOKEN" \ -e APP_INSTANCE_IMAGE="nano/app-instance:latest" \ -e APP_INSTANCE_NETWORK_NAME="$NANO_NET" \ -e APP_INSTANCE_PROVIDER="$NANO_PROVIDER" \ -e APP_INSTANCE_MODEL="$NANO_MODEL" \ -e APP_INSTANCE_API_KEY="$NANO_API_KEY" \ -e APP_INSTANCE_API_BASE="$NANO_API_BASE" \ -e DEFAULT_AUTHZ_BASE_URL="$NANO_AUTHZ_URL" \ -e DEFAULT_AUTHZ_OUTLOOK_MCP_URL="$NANO_OUTLOOK_MCP_URL" \ -e DEFAULT_OUTLOOK_MCP_SERVER_ID="$NANO_OUTLOOK_MCP_SERVER_ID" \ -e DEPLOY_PUBLIC_SCHEME="http" \ -e DEPLOY_PUBLIC_BASE_DOMAIN="$NANO_BASE_DOMAIN" \ -e DEPLOY_PUBLIC_PORT="8088" \ -e DEPLOY_AUTO_START_PROXY="1" \ nano/deploy-control:latest ``` ### 这一步在做什么 `deploy-control` 会负责: - 收到“创建实例”的请求 - 调用 `app-instance/create-instance.sh` - 通过 Docker 创建对应用户实例 - 刷新 `router-proxy` --- ## 9. 启动 `auth-portal` ```bash docker rm -f nano-auth-portal >/dev/null 2>&1 || true docker run -d \ --name nano-auth-portal \ --restart unless-stopped \ --network "$NANO_NET" \ -p 3081:3081 \ -e AUTHZ_API_BASE_URL="$NANO_AUTHZ_URL" \ -e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \ -e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \ nano/auth-portal:latest ``` 这个页面就是用户看到的登录/注册入口。 虽然注册入口主要依赖 `AUTHZ_API_BASE_URL`,这里还是建议把 `DEPLOY_API_BASE_URL` 一起带上并确认非空,避免后面运行态调用 deploy-control 时再踩同一个坑。 启动完可以确认: ```bash docker inspect nano-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)=' ``` --- ## 10. 做健康检查 ### 先检查接口 ```bash curl http://127.0.0.1:19090/healthz curl http://127.0.0.1:8090/healthz curl -I http://127.0.0.1:3081 ``` 你应该大致看到: - `authz-service` 返回健康 JSON - `deploy-control` 返回健康 JSON - `auth-portal` 返回 `HTTP/1.1 200 OK` ### 再看容器状态 ```bash docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' ``` 你至少应该能看到这些容器: - `nano-authz-service` - `nano-deploy-control` - `nano-auth-portal` - `nano-router-proxy` ### 再看一下代理日志 ```bash docker logs --tail=50 nano-router-proxy ``` 如果这一步没有明显报错,就可以开始浏览器测试了。 --- ## 11. 浏览器首次测试 打开: ```text http://127.0.0.1:3081/register ``` 然后按顺序操作: 1. 注册一个新账号 2. 注册成功后,系统会自动创建一个你的专属实例 3. 浏览器应该跳到你的实例地址 跳转目标一般长这样: ```text http://你的slug.127.0.0.1.nip.io:8088 ``` 例如: ```text http://alice.127.0.0.1.nip.io:8088 ``` --- ## 12. 确认实例真的被创建出来了 ```bash cd "$PROJECT_ROOT/app-instance" ./list-instances.sh ./list-instances.sh --json ``` 你应该能看到类似: - `instance_id` - `instance_slug` - `container_name` - `public_url` 以及对应的 `app-instance-` 容器。 你还可以继续查: ```bash docker ps --format 'table {{.Names}}\t{{.Status}}' | grep app-instance ``` --- ## 13. 如果你只是想单独看前端页面 如果你只是想看 `auth-portal` 页面样子,不跑全链路,也可以单独启动它的前端开发模式: ```bash cd /home/ivan/xuan/nano_project/auth-portal/src npm install npm run dev ``` 然后打开: ```text http://127.0.0.1:3081 ``` 但是要注意: - 这只能看页面 - 注册、登录、创建实例这些动作是否成功,仍然取决于 `authz-service` 和 `deploy-control` 有没有另外启动 --- ## 14. 一键排错命令 如果你感觉“不对劲”,先跑这几条: ```bash docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' docker logs --tail=100 nano-authz-service docker logs --tail=100 nano-deploy-control docker logs --tail=100 nano-auth-portal docker logs --tail=100 nano-router-proxy curl http://127.0.0.1:19090/healthz curl http://127.0.0.1:8090/healthz curl -I http://127.0.0.1:3081 ``` 如果是实例创建失败,再加两条: ```bash cd "$PROJECT_ROOT/app-instance" ./list-instances.sh --json docker ps --format 'table {{.Names}}\t{{.Status}}' | grep app-instance ``` 如果注册页弹出: ```text 502: Request URL is missing an 'http://' or 'https://' protocol. ``` 优先查这两条: ```bash docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)=' docker inspect nano-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)=' ``` 重点看: - `nano-authz-service` 里的 `DEPLOY_API_BASE_URL` - `nano-auth-portal` 里的 `AUTHZ_API_BASE_URL` 它们都必须是完整 URL,不能是空字符串,也不能是裸 `host:port`。 如果你已经改过 `export NANO_DEPLOY_URL=...`,但这里查出来还是空,说明你只是改了当前 shell 变量,没有把容器重建掉。 这时直接按下面重建: ```bash export NANO_AUTHZ_URL='http://nano-authz-service:19090' export NANO_DEPLOY_URL='http://nano-deploy-control:8090' export NANO_DEPLOY_TOKEN="$(docker inspect nano-deploy-control --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^DEPLOY_CONTROL_API_TOKEN=//p')" export NANO_AUTHZ_INTERNAL_TOKEN="$(docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^AUTHZ_INTERNAL_TOKEN=//p')" docker rm -f nano-authz-service >/dev/null 2>&1 || true docker run -d \ --name nano-authz-service \ --restart unless-stopped \ --network "$NANO_NET" \ -p 19090:19090 \ -v "$PROJECT_ROOT/authz-service/runtime/data:/var/lib/authz-service/data" \ -e AUTHZ_ISSUER="$NANO_AUTHZ_URL" \ -e AUTHZ_INTERNAL_TOKEN="$NANO_AUTHZ_INTERNAL_TOKEN" \ -e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \ -e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \ nano/authz-service:latest docker rm -f nano-auth-portal >/dev/null 2>&1 || true docker run -d \ --name nano-auth-portal \ --restart unless-stopped \ --network "$NANO_NET" \ -p 3081:3081 \ -e AUTHZ_API_BASE_URL="$NANO_AUTHZ_URL" \ -e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \ -e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \ nano/auth-portal:latest ``` 重建后再确认: ```bash docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)=' docker inspect nano-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)=' ``` 你必须看到: ```text DEPLOY_API_BASE_URL=http://nano-deploy-control:8090 ``` --- ## 15. 最常见的坑 ### 1. API Key 没填 现象: - 注册页面提交后创建实例失败 原因: - `APP_INSTANCE_API_KEY` 没有有效值 ### 2. Docker 没启动 现象: - `deploy-control` 无法创建实例 - 或 `docker ps` 本身就报错 ### 3. `AUTHZ_ISSUER` 写成了 `127.0.0.1` 错误写法: ```text http://127.0.0.1:19090 ``` 正确写法: ```text http://nano-authz-service:19090 ``` 原因: - 新实例容器里访问不到宿主机自己的 `127.0.0.1:19090` ### 4. `deploy-control` 的路径挂载写错 错误思路: - 把宿主机的 `app-instance` 挂到容器里的 `/app-instance` 正确思路: - 宿主机原路径挂进去 - 并设置: - `APP_INSTANCE_DIR="$PROJECT_ROOT/app-instance"` - `ROUTER_PROXY_DIR="$PROJECT_ROOT/router-proxy"` ### 5. `NANO_DEPLOY_URL` 或 `NANO_AUTHZ_URL` 没带 `http://` 典型现象: - 注册页直接弹: - `502: Request URL is missing an 'http://' or 'https://' protocol.` 原因: - `authz-service` 用 `DEPLOY_API_BASE_URL` 去调 `deploy-control` - `auth-portal` 用 `AUTHZ_API_BASE_URL` 去调 `authz-service` - 这些值如果是空,或者写成 `nano-deploy-control:8090` 这种不带协议的字符串,请求会直接失败 - 就算你后来在 shell 里改对了,如果没重建相关容器,老的错误值仍然会继续生效 正确写法: ```text http://nano-authz-service:19090 http://nano-deploy-control:8090 ``` ### 6. `nip.io` 解析失败 如果实例跳转地址打不开,先试: ```bash ping 127.0.0.1.nip.io ``` 如果你本地网络把 `nip.io` 拦了,这套子域名测试方式就会失效。 ### 7. 端口被占用 默认会用到这些端口: - `3081` - `8090` - `19090` - `8088` 你可以先查: ```bash ss -ltnp | grep -E '3081|8090|19090|8088' ``` --- ## 16. 如果你要重新来一遍 如果你只是想“重新部署这四个基础容器”,可以先停掉它们: ```bash docker rm -f \ nano-auth-portal \ nano-authz-service \ nano-deploy-control \ nano-router-proxy 2>/dev/null || true ``` 如果你还想把旧实例容器也一起清掉,再额外处理 `app-instance-*`。 注意: - 不要在你还需要旧数据的时候乱删 `runtime/` - `authz-service/runtime/data` 和 `app-instance/runtime/instances` 里都有持久化数据 --- ## 17. 本机部署成功后的结果应该是什么 如果整个流程正常,最后你会得到: - 一个可以打开的注册页: - `http://127.0.0.1:3081/register` - 一个统一实例入口代理: - `http://.127.0.0.1.nip.io:8088` - 一个能自动创建用户专属容器的部署控制面 - 一份实例注册表: - `app-instance/runtime/registry/instances.json` --- ## 18. 你下一步最建议做什么 第一次建议这样测: 1. 用全新用户名注册一个测试账号 2. 确认浏览器跳到了你的实例 URL 3. 再执行 `./app-instance/list-instances.sh --json` 4. 确认注册表里真的有这条实例记录 如果你后面想要,我还可以继续补两份文档: - `服务器部署指南.md` - 面向公网服务器、固定 IP、长期运行 - `常见报错排查.md` - 专门收集 502、超时、实例起不来、MCP 鉴权失败这类问题