feat: add one-click Deploy to Render support
Adds a Render Blueprint (render.yaml) and supporting pieces so nanobot can
be deployed to Render in one click, with persistent memory across deploys.
- render.yaml: web service + 1GB persistent disk mounted at
/home/nanobot/.nanobot. Prompts for ANTHROPIC_API_KEY and
NANOBOT_WEB_TOKEN at deploy time (sync: false).
- render-config.json: committed gateway config that wires secrets via
${VAR} placeholders (resolved at runtime). Nothing secret is committed.
- entrypoint.sh: adds a branch gated on RENDER=true that copies the config
onto the mounted disk, chowns the root-owned mount, and drops to the
non-root nanobot user via setpriv. Local (non-Render) path is unchanged.
- Dockerfile: COPY render-config.json; USER nanobot -> USER root so the
entrypoint can chown the freshly-mounted disk before dropping privileges;
add PYTHONUNBUFFERED/PYTHONFAULTHANDLER for diagnosable crash output.
- README.md: Deploy to Render button + section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
Claude Opus 4.8
parent
afed32b013
commit
770d89b430
+13
-1
@@ -28,6 +28,12 @@ COPY nanobot/ nanobot/
|
||||
COPY --from=webui-builder /app/nanobot/web/dist/ nanobot/web/dist/
|
||||
RUN NANOBOT_SKIP_WEBUI_BUILD=1 uv pip install --system --no-cache ".[$NANOBOT_EXTRAS]"
|
||||
|
||||
# Render deploy template (see render.yaml): committed gateway config that wires
|
||||
# secrets through ${ANTHROPIC_API_KEY} / ${NANOBOT_WEB_TOKEN} env vars (resolved
|
||||
# at startup). Lives in the code dir (/app), not the data dir, so a mounted disk
|
||||
# won't shadow it. Only used when RENDER=true; ignored by local runs.
|
||||
COPY render-config.json ./
|
||||
|
||||
# Create non-root user and config directory
|
||||
RUN useradd -m -u 1000 -s /bin/bash nanobot && \
|
||||
mkdir -p /home/nanobot/.nanobot && \
|
||||
@@ -36,8 +42,14 @@ RUN useradd -m -u 1000 -s /bin/bash nanobot && \
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/entrypoint.sh && chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
USER nanobot
|
||||
# Start as root so the Render entrypoint can chown the freshly-mounted
|
||||
# (root-owned) persistent disk, then drop to the non-root nanobot user via
|
||||
# setpriv (see entrypoint.sh). Local runs without a disk are unaffected.
|
||||
USER root
|
||||
ENV HOME=/home/nanobot
|
||||
# Ensure crash output reaches Render logs (app output is otherwise swallowed on
|
||||
# non-graceful exit).
|
||||
ENV PYTHONUNBUFFERED=1 PYTHONFAULTHANDLER=1
|
||||
|
||||
# Gateway health endpoint and optional WebUI/WebSocket channel ports
|
||||
EXPOSE 18790 8765
|
||||
|
||||
@@ -46,6 +46,13 @@
|
||||
| Connect Telegram, Discord, WeChat, Slack, Email, Mattermost, or another chat app | [Chat Apps](./docs/chat-apps.md) |
|
||||
| Configure providers, fallback models, Langfuse, MCP, web tools, or security | [Docs](./docs/README.md) and [Configuration](./docs/configuration.md) |
|
||||
| Understand or extend the internals | [Architecture](./docs/architecture.md) and [Development](./docs/development.md) |
|
||||
| Deploy to the cloud in one click | [Deploy to Render](#deploy-to-render) |
|
||||
|
||||
## Deploy to Render
|
||||
|
||||
Deploy nanobot's gateway and bundled WebUI as a single web service with persistent memory. Render reads [`render.yaml`](./render.yaml) and prompts for two secrets on deploy: `ANTHROPIC_API_KEY` and `NANOBOT_WEB_TOKEN` (the password that gates the public WebUI — generate a strong random value, e.g. `openssl rand -hex 32`).
|
||||
|
||||
[](https://render.com/deploy?repo=https://github.com/HKUDS/nanobot)
|
||||
|
||||
## What can nanobot do?
|
||||
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
#!/bin/sh
|
||||
dir="$HOME/.nanobot"
|
||||
|
||||
# Render deploy path (see render.yaml + render-config.json). Gated on Render's
|
||||
# automatic RENDER=true env var so local Docker/podman usage is unaffected.
|
||||
# Copies the committed config template onto the mounted disk (wiring secrets via
|
||||
# ${VAR} env vars and keeping runtime data on the persistent disk), chowns the
|
||||
# root-owned mount, then drops to the non-root nanobot user. Logs each decision
|
||||
# so a failed start is diagnosable in Render's logs.
|
||||
if [ "$RENDER" = "true" ]; then
|
||||
echo "[entrypoint] Render deploy — starting as $(id)"
|
||||
mkdir -p "$dir" || echo "[entrypoint] warning: mkdir $dir failed"
|
||||
config="$dir/config.json"
|
||||
cp /app/render-config.json "$config" || echo "[entrypoint] warning: cp config failed"
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
chown -R nanobot:nanobot "$dir" || echo "[entrypoint] warning: chown $dir failed"
|
||||
if setpriv --reuid=nanobot --regid=nanobot --init-groups true 2>/dev/null; then
|
||||
echo "[entrypoint] dropping privileges to nanobot via setpriv"
|
||||
exec setpriv --reuid=nanobot --regid=nanobot --init-groups nanobot "$@" --config "$config"
|
||||
fi
|
||||
echo "[entrypoint] setpriv privilege-drop not permitted — running as root"
|
||||
fi
|
||||
exec nanobot "$@" --config "$config"
|
||||
fi
|
||||
|
||||
if [ -d "$dir" ] && [ ! -w "$dir" ]; then
|
||||
owner_uid=$(stat -c %u "$dir" 2>/dev/null || stat -f %u "$dir" 2>/dev/null)
|
||||
cat >&2 <<EOF
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "anthropic/claude-opus-4-8",
|
||||
"provider": "auto"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"anthropic": {
|
||||
"apiKey": "${ANTHROPIC_API_KEY}"
|
||||
}
|
||||
},
|
||||
"gateway": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18790
|
||||
},
|
||||
"channels": {
|
||||
"websocket": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 8765,
|
||||
"token": "${NANOBOT_WEB_TOKEN}",
|
||||
"websocketRequiresToken": true
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"restrictToWorkspace": true,
|
||||
"webuiAllowRemotePackageInstall": false,
|
||||
"my": {
|
||||
"allowSet": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# Render Blueprint — deploys nanobot's gateway + bundled WebUI as one web
|
||||
# service. Secrets are provided at deploy time as env vars (sync: false) and
|
||||
# resolved at runtime via the ${VAR} placeholders in render-config.json.
|
||||
# Nothing secret is stored in this repo.
|
||||
services:
|
||||
- type: web
|
||||
name: nanobot
|
||||
runtime: docker
|
||||
dockerfilePath: ./Dockerfile
|
||||
dockerContext: .
|
||||
# Render's Docker Command REPLACES the Dockerfile ENTRYPOINT (it is not
|
||||
# appended to it), so invoke the entrypoint explicitly. When RENDER=true the
|
||||
# entrypoint copies render-config.json onto the mounted disk at
|
||||
# $HOME/.nanobot/config.json (so the runtime data_dir lands on the persistent
|
||||
# disk), chowns the root-owned mount, drops to the non-root nanobot user, and
|
||||
# appends the --config flag itself — so pass only `gateway` here.
|
||||
dockerCommand: /usr/local/bin/entrypoint.sh gateway
|
||||
plan: starter
|
||||
healthCheckPath: /
|
||||
envVars:
|
||||
# Anthropic API key — powers the agent's LLM calls. Get one at
|
||||
# https://console.anthropic.com/settings/keys
|
||||
- key: ANTHROPIC_API_KEY
|
||||
sync: false
|
||||
# WebUI access secret — the gate for the public WebUI. Generate a strong
|
||||
# random value (e.g. `openssl rand -hex 32`) and keep it private.
|
||||
- key: NANOBOT_WEB_TOKEN
|
||||
sync: false
|
||||
# Port Render routes public traffic to; matches channels.websocket.port
|
||||
# in render-config.json.
|
||||
- key: PORT
|
||||
value: 8765
|
||||
# Persist sessions, memory, and the WebUI display transcripts across deploys.
|
||||
# The entrypoint copies the config onto this mount, so nanobot's runtime
|
||||
# data_dir (config_path.parent) resolves here too — keeping webui/ (chat
|
||||
# history the UI renders), cron, media, and logs durable, not just session
|
||||
# files. Starter / 1 GB is the lean default; history persistence needs this
|
||||
# disk, not a bigger plan.
|
||||
disk:
|
||||
name: nanobot-data
|
||||
mountPath: /home/nanobot/.nanobot
|
||||
sizeGB: 1
|
||||
Reference in New Issue
Block a user