推送与部署(普通应用)
普通应用 = 你把镜像推到平台分配的私有仓库,平台轮询到新镜像后自动部署。
本页讲从 nae create 到 running 的完整闭环、镜像必须满足的运行时约束、以及怎么排查部署失败。
(模板应用走另一条路:一次性部署、镜像 pin 在模板版本,见模板。)
一条龙
# 1) 建应用:拿到推送地址,此时状态 awaiting_image(等首次推镜像)
nae create --app-id myweb --tag latest --port 8080 --cpu 500m --mem 512Mi
nae app myweb # 看 PushCN / PushGlobal(推送地址)与 Route(访问地址)
# 2) 构建并推送镜像到上一步拿到的地址(无需 docker login,见下)
docker build --platform linux/amd64 -t <PushCN 或 PushGlobal>:latest .
docker push <PushCN 或 PushGlobal>:latest
# 3) 平台轮询检测到 latest 标签的新 digest → 自动部署 → running。无需手动「deploy」
nae app myweb # 等状态变 running
nae events myweb # 看 image_detected → deploy_started → running 时间线
没有 Docker?用 nae build 让平台代构建(备选)
推荐:本机有 Docker 就用上面的本地构建 + 推送。 本地构建全量复用你机器上的层缓存、 不占上传带宽、不受上下文 200MB 上限与服务端排队影响,构建出问题也最好排查。 线上代构建(
nae build/ 网页上传构建)仅作本机没有 Docker 时的备选,不建议当作主力发布方式。
本机没装 Docker、或不方便本地构建,就把带 Dockerfile 的项目目录直接交给平台——平台替你
docker build + docker push,推完照样触发上面的自动部署闭环。
# 在项目根目录(有 Dockerfile 的地方)
nae build myweb # 打包当前目录 → 上传 → 排队构建 → 跟随日志直至结束
nae build myweb ./svc --tag v2 # 指定上下文目录与镜像 tag
nae build myweb --dockerfile docker/Dockerfile.prod # Dockerfile 不在根/不叫 Dockerfile
要点:
- 务必写
.dockerignore:打包遵循上下文根的.dockerignore,把node_modules/、构建产物、.git/等排除掉。压缩后上限 200MB,不排除很容易超。 - 构建在服务端排队:服务端忙时任务排队,日志会显示「前面还有 N 个」。
- 构建成功即自动部署:平台把镜像推到该应用的私有仓库(等价于你本地
docker push), 轮询到新 digest 就滚动部署——和上面的闭环完全一致。 - 镜像运行时约束同样适用(非 root、端口 ≥ 1024,见下文);Dockerfile 由你提供,平台只负责构建。
查状态与日志(nae build 默认已跟随;断线或用了 --no-follow 时再看):
nae builds myweb # 该应用最近构建任务(状态 / 排队位置)
nae build-logs <job> -f # 跟随某任务日志直到结束(游标续读,断线不丢)
nae build-cancel <job> # 取消排队中的任务(仅 queued 可取消,已开始的不能中断)
网页端「应用详情 → 构建」Tab 同样能提交构建:点「上传构建」选中项目文件夹,浏览器就地打成 构建上下文直传,效果与
nae build一致;也能看历史/排队位置/日志、取消排队中的任务。
推送镜像
- 锁定
--platform linux/amd64:集群是 amd64。ARM Mac 默认构建出 arm64 镜像,推上去会exec format error起不来。 - 无需
docker login:从受信任的构建网络直接docker push即可。推送地址(PushCN/PushGlobal) 是平台为这个应用分配的私有仓库地址,请妥善保管、切勿对外泄露。 - 平台自动部署,不需要你手动触发:平台轮询仓库,检测到你
create时指定的--tag(默认latest) 出现新 digest,就自动拉取并滚动部署。想发新版本,重新 build+push 同一个 tag 即可。 - 已有本地镜像不想重新构建,可改用 tag 再推:
docker tag <本地镜像:tag> <PushCN 或 PushGlobal>:latest docker push <PushCN 或 PushGlobal>:latest
镜像运行时约束(重要)
平台以非 root 用户运行容器,且容器无新增特权能力。镜像必须满足:
- 能以非 root 用户启动——启动时不要
chown、不要写系统目录(/etc、/var/cache等)。 缓存/临时文件请写/tmp或挂载的可写卷。 - 监听端口 ≥ 1024——非 root 不能绑定 1024 以下端口(如 80/443)。让程序监听如
8080, 并把它填进--port。
不满足会在部署阶段崩溃。典型坑:官方
nginx:alpine启动即chown("/var/cache/nginx/...") failed (Operation not permitted),且默认监听 80。 换 rootless 基底并改监听端口即可。
已验证可用的 nginx 静态站点最小示例:
# 用 unprivileged 基底,不 chown、不抢低端口
FROM nginxinc/nginx-unprivileged:alpine
COPY dist/ /usr/share/nginx/html/
# 该镜像默认就监听 8080、以非 root 跑,无需再改
nae create --app-id mysite --tag latest --port 8080
部署状态怎么看
应用整体状态 nae app <id> 的 Status 只有这几种:
| Status | 含义 |
|---|---|
awaiting_image |
普通应用已建、等首次推镜像 |
running |
在跑 |
stopped |
已停止(缩到 0 副本) |
deleting |
删除中(模板级联清理) |
「正在部署 / 部署失败」不是应用状态,而是某个镜像版本的状态——见 nae versions <id> 里每条版本的
Status:deploying / succeeded / failed。最新版本 failed 就是这次部署没起来。
排查部署失败:
nae versions myweb # 哪个版本 failed
nae events myweb # 时间线:image_detected / deploy_started / deploy_failed
nae logs myweb # 容器日志(崩溃原因,如上面的 chown 报错)
前端 SPA 注意事项(路径前缀)
普通应用默认剥离 /apps/<appid> 前缀:外部请求 …/apps/myweb/api/health 到容器里是 /api/health。
这对「在根路径服务」的程序刚好。但前端 SPA 用绝对路径引用静态资源会 404:
- Vite 默认
base:'/'→ 产物里<script src="/assets/x.js">,浏览器解析到站点根/assets/x.js, 而不是/apps/myweb/assets/x.js→ 404。 - 解法:构建时设相对 base。Vite 设
base: './',资源变成./assets/x.js,在任意挂载路径下都对。
若你的框架自带 basePath(如 Next.js Node server 已配 /apps/myweb),则需要保留前缀:
创建时加 --keep-path-prefix,让平台不剥离。
# 自带 basePath 的框架:保留前缀
nae create --app-id mynext --tag latest --port 3000 --keep-path-prefix
# 建好后想切换前缀策略,不必删库重建:
nae update mynext --strip-path-prefix # 改回剥离
nae update mynext --keep-path-prefix # 改成保留
让普通应用连上模板应用
模板应用(PostgreSQL/Redis 等)create 返回的 Route 就是集群内 DNS 主机名,形如
app-<id>-<appid>.<namespace>.svc.cluster.local:<port>。把它填进业务应用的 env 即可连上:
# 1) 建模板,记下各自的 Route 主机名
nae create --kind template --app-id mypg --template postgres --storage 5Gi \
--config '{"database":"appdb","username":"appuser","password":"<pg密码>"}'
nae create --kind template --app-id myredis --template redis --storage 2Gi \
--config '{"password":"<redis密码>"}'
# 2) 业务应用把模板的 Route 主机名 + 端口填进 env(用 --env-file 避免 shell 引号问题)
cat > env.json <<'EOF'
{
"PGHOST": "app-<id>-mypg.<ns>.svc.cluster.local",
"PGPORT": "5432",
"PGUSER": "appuser",
"PGPASSWORD": "<pg密码>",
"PGDATABASE": "appdb",
"REDIS_HOST": "app-<id>-myredis.<ns>.svc.cluster.local",
"REDIS_PORT": "6379"
}
EOF
nae create --app-id myapi --tag latest --port 8080 --env-file env.json
<id>/<ns>以nae app mypg返回的Route为准,直接抄那个主机名即可。 环境变量事后要改用nae update myapi --env-file env.json(见应用管理),不必删库重建。