Deployment runbook
Deploy something you have already proved locally.
The generated manifests are reviewable starting points. Cloud interfaces, plans, and manifest schemas change. Preview cost and configuration before creating resources, keep secrets in the provider’s secret store, and verify the deployed process instead of assuming a green build means a working bot.
Choose the right process type
| Bot platform | Cloud process | Public HTTP required? |
|---|---|---|
| Web chat | Web service | Yes; port supplied by the platform or 8000. |
| Discord | Continuous worker | No; it opens an outbound gateway connection. |
| Telegram | Continuous worker | No for the generated long-polling adapter. |
| Terminal | Interactive local process | No. Do not deploy it as an unattended worker. |
Discord and Telegram need exactly one active polling/gateway process for a simple deployment. Multiple Telegram long-polling instances can compete for updates. Web chat can scale horizontally only if you accept per-instance SQLite memory or replace the local state design.
Pre-deployment checklist
- Complete the local setup runbook with the same provider, model, and platform.
- Run
python bot.py --checkand one real reply. - Create a private Git repository if the cloud platform deploys from Git. Confirm
.env,memory.db, logs, and private knowledge are absent. - Choose a region near the model endpoint and intended users.
- Decide whether memory must survive deploys. Most platform filesystems are ephemeral unless a volume/disk is attached.
- Set provider budgets, rate limits, and alerts before making the service public.
- For web chat, generate a long random
BOT_ACCESS_TOKENand plan TLS plus upstream abuse controls.
# Inspect exactly what Git would receive git status --short git check-ignore .env memory.db # Verify package before cloud work python bot.py --check python -m py_compile bot.py
Docker on one machine
This is the closest local rehearsal for every Docker-based cloud target.
cp .env.example .env # Edit .env privately, then: docker compose config docker compose build --pull docker compose up -d docker compose ps docker compose logs --tail=100 bot # Web export only curl --fail http://127.0.0.1:8000/healthz
The Compose project mounts knowledge/ read-only and stores memory.db in the named bot-data volume through BOT_DATA_DIR=/data. Updating the image does not remove that volume.
# Safe application update; retains bot-data docker compose build --pull docker compose up -d # Stop and retain memory docker compose down # DANGER: also deletes persisted bot memory docker compose down -v
The generated token protects /chat, but a public service also needs HTTPS, firewall/security-group review, request and bandwidth limits, log review, and a plan for abuse. Keep /healthz public only if your platform requires it; it returns status, bot name, and template version.
Generic Linux VPS
- Create a non-root deployment user and install current Docker Engine plus the Compose plugin from your distribution’s trusted instructions.
- Copy or clone the project into a dedicated directory. Create
.envon the server with restrictive permissions. - Allow inbound SSH only from trusted networks. For web chat, expose 80/443 through a reverse proxy; do not expose the model server.
- Run Compose, verify health locally, then configure the proxy to forward HTTPS to
127.0.0.1:8000. - Enable security updates, disk monitoring, log rotation, and provider spend alerts.
chmod 600 .env docker compose up -d --build curl --fail http://127.0.0.1:8000/healthz docker compose logs --tail=100 bot
Back up the named volume only if you need conversation history. Treat backups as sensitive because they may contain user messages. Test restore to a disposable volume before depending on it.
Railway
Railway detects a root-level Dockerfile. Its current documentation explains that Compose concepts are translated into Railway services; the platform does not simply execute docker-compose.yml as a production stack.
- Push the extracted project to a private GitHub repository without
.env. - Create a Railway project and add a service from that repository. Confirm the build log says it detected the Dockerfile.
- In Variables, add the one provider key you need,
LLM_BASE_URLif overridden, the platform token, optionalAMT_AGENT_KEY, andBOT_ACCESS_TOKENfor web. - For web, set
HOST=0.0.0.0. The generated runtime reads Railway’s injectedPORT; if you configure a target port manually, ensure the service and health check agree. - Generate a public domain only for web chat. Workers need no public domain.
- If SQLite memory must persist, attach a Railway Volume at
/dataand setBOT_DATA_DIR=/data. - Set the web health-check path to
/healthz, deploy, then inspect build and deployment logs.
# Optional Railway CLI checks after linking the project railway status railway logs
See Railway’s official Dockerfile, variables, health-check, and Compose translation documentation.
Render
The included render.yaml defines a Docker web service for web chat or a Docker worker for Discord/Telegram. Render’s current Blueprint specification allows the free plan for web services but not background workers; the generated worker therefore uses Starter.
- Push the project to a private repository and choose New Blueprint in Render.
- Review the proposed service type, region, and plan before applying.
- Add credentials through the dashboard. Do not hardcode secrets into
render.yaml. For a custom Blueprint, secret placeholders may usesync: false. - For web, set
HOST=0.0.0.0andBOT_ACCESS_TOKEN. The manifest checks/healthz. - If memory must persist, attach a paid persistent disk at
/dataand setBOT_DATA_DIR=/data. Render filesystems are otherwise ephemeral. - Deploy, open the Events and Logs views, verify health, and send one private test message.
Render preserves existing environment variables omitted during later Blueprint syncs, but new sync: false variables on an existing service must be added manually. Consult the official Blueprint reference, environment-variable guide, and persistent-disk behavior.
Fly.io
- Install and authenticate
flyctl, then reviewfly.toml. Change the globally unique app name and preferred region. - Create the app configuration without deploying.
- Set secrets through Fly’s encrypted secrets interface. Setting secrets updates Machines, so plan this as a deployment change.
- If persistent memory is required, create a volume in the same region, add a mount at
/data, and setBOT_DATA_DIR=/databefore deployment. - Deploy and verify status, logs, and health checks.
fly launch --copy-config --no-deploy fly secrets set BOT_ACCESS_TOKEN=replace_privately # Also set exactly the provider/platform keys required by this bot fly secrets list fly deploy fly status fly checks list fly logs
Web exports bind Waitress to 0.0.0.0 on internal port 8000 in the generated configuration. Add an HTTP health check for /healthz if your reviewed fly.toml does not contain one. Read Fly’s official secrets, health checks, and HTTP service references.
DigitalOcean App Platform
- Replace
YOUR_GITHUB_REPOSITORYin.do/app.yamland connect the private repository. - Use the App Platform spec preview or control panel to confirm the component is a service for web or worker for Discord/Telegram.
- Add provider and platform credentials as encrypted
SECRET, runtime-scoped environment variables. Do not place their values in the spec. - For web, keep
HOST=0.0.0.0, port 8000, and add an HTTP health check for/healthz. - Review instance size, region, alerts, deployment source, and estimated cost before creation.
- Deploy, inspect runtime logs, verify health, and send a controlled test message.
The generated spec is intentionally minimal. Validate it against DigitalOcean’s current App Spec reference and health-check guide. If durable SQLite memory is a requirement, confirm the platform’s current storage option before relying on local disk; otherwise choose a VPS with an explicit volume.
Private sharing with Tailscale
For a web bot intended only for your tailnet, keep the bot on loopback and use Tailscale Serve as the private HTTPS edge:
# Bot .env HOST=127.0.0.1 PORT=8000 BOT_ACCESS_TOKEN=replace_with_a_long_random_value python bot.py # In another terminal, using current Tailscale CLI syntax: tailscale serve 8000 tailscale serve status
Tailscale documents Serve for tailnet-only sharing. Funnel is public internet exposure and should not be treated as equivalent; review the Funnel security and availability model before using it.
Post-deployment verification
# Web service
curl --fail https://YOUR_DOMAIN/healthz
# Confirm chat rejects a missing token
curl -sS -o /dev/null -w '%{http_code}\n' \
-H 'content-type: application/json' \
--data '{"message":"health test"}' \
https://YOUR_DOMAIN/chat
# Expected when BOT_ACCESS_TOKEN is configured: 401- Send one authenticated web message or one private Discord/Telegram message.
- Confirm the response comes from the intended model, not a fallback.
- Restart/redeploy once and verify required persistence.
- Inspect logs for tracebacks, repeated restarts, secrets, prompts, and private file content.
- Check model-provider usage and cost after the test.
- Record the deployed commit or archive, configuration schema/template version, secret names, region, volume, and rollback procedure.
Update and rollback
- Export or retain the previous
config.jsonand deployment files. - Back up
memory.dbonly if retention is required; never copy it into Git. - Import the old configuration into Bot Builder, review normalization, and download the new maintained template.
- Test locally and in a separate preview/staging service.
- Deploy the new image while preserving the same secret names and volume mount.
- If health or the private smoke test fails, redeploy the prior known-good image/commit. Restore memory only if the release changed or damaged it.
When SQLite memory is mounted persistently, rolling back the container leaves the current database in place. Keep the failed state for diagnosis and restore a backup only when necessary.