推送与部署(普通应用)
普通应用 = 你把镜像推到平台分配的私有仓库,平台轮询到新镜像后自动部署。
本页讲从 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 时间线
推送镜像
- 锁定
--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(见应用管理),不必删库重建。