Deploy Comis to Render.com from Docker Hub — managed Linux host, automatic HTTPS, persistent storage
This guide deploys Comis to Render.com using the prebuilt
Docker Hub image. You get a managed Linux host (so the exec sandbox is fully
active — unlike Docker Desktop on macOS), automatic HTTPS, a persistent volume
for memory and configuration, and a public URL within ~5 minutes. No Docker
installed locally, no server to provision, no kernel to manage.
You don’t need to understand the technical details to use this feature. The configuration examples below are copy-paste ready.
Why Render specifically: It’s the simplest “click-deploy a Docker Hub image
with a persistent disk and an HTTPS endpoint” PaaS. The same general pattern
works on Fly.io, Railway, DigitalOcean App Platform, and AWS ECS — adjust the
service-creation flow but keep the environment variables and disk mount the same.
Telegram BotFather, Discord Developer Portal, Slack app, etc.
A 32-byte hex string
Run openssl rand -hex 32 locally — you’ll use this as the gateway bearer token
Credit card on Render
Required for the Starter plan ($7/mo). The free plan does not work for Comis — see Plan choice below.
You don’t need Docker, Node.js, or anything else installed on your machine.
Render pulls the image directly from Docker Hub.
Plan choice. Render’s free plan has no persistent disk and sleeps after
15 minutes of inactivity. Comis needs both — without a disk every restart
wipes your agent’s memory and conversations; without always-on the agent
can’t respond to channel events. Use the $7/mo Starter plan or higher.
Log into Render → click New + in the top right → Web Service.On the next screen, look for the “Existing Image” option (not “Public Git Repository”
— we’re deploying a prebuilt image, not source). Click it.
2
Point at the Comis Docker Hub image
In the Image URL field, paste:
docker.io/comisai/comis:latest-slim
For production, pin to an immutable version tag instead of latest-slim so
your service doesn’t update under you. Find the latest version on the
Comis Docker Hub page and use
e.g. docker.io/comisai/comis:1.0.42-slim.
Click Connect (or “Next” depending on your Render UI version).
3
Configure the service
Fill in:
Field
Value
Name
comis (or anything you like — becomes part of your URL)
Region
Pick the one closest to you/your users
Instance Type
Starter ($7/mo) — Free won’t work, see warning above
Branch / Tag
leave blank (we’re using a tag-pinned image)
Dockerfile Path
leave blank
Docker Build Context Directory
leave blank
Render will detect the image’s exposed port. Comis exposes 4766.
4
Add environment variables
Scroll down to Environment Variables and add each of these. Use the
“Add Secret File” / “Add” button for each row. Mark each as a Secret
(lock icon) so the values stay hidden in logs and the dashboard.Required:
COMIS_GATEWAY_HOST=0.0.0.0COMIS_GATEWAY_PORT=4766COMIS_GATEWAY_TOKEN=<paste the openssl rand -hex 32 output here>ANTHROPIC_API_KEY=sk-ant-api03-...
Optional channel tokens — add only those you’ll use:
Never store API keys, tokens, or passwords directly in config.yaml. Use the .env file or Secret Manager for credential management.
The encrypted secrets store is enabled by default. On first boot, Comis
auto-generates a master key and writes it to ~/.comis/.env (mode 0600).
Back up this file — losing SECRETS_MASTER_KEY makes secrets.db
permanently unreadable. To disable the encrypted store, set
security.storage: env (or file) in config.yaml.
5
Add a persistent disk
Still on the create-service screen, scroll to Advanced → expand it →
look for Add Disk. Configure:
Field
Value
Name
comis-data
Mount Path
/home/comis/.comis
Size
1 GB (you can grow it later)
This is where your agent’s memory database, logs, secrets store, and
workspace live. Without this disk, every redeploy wipes your data.
6
Set the health check
Scroll to Health Check Path and enter:
/health
Render uses this to decide when traffic should start flowing to your
container after a deploy. Comis’s daemon serves a JSON {"status":"ok",...}
on this endpoint as soon as the gateway is up.
7
Deploy
Click Create Web Service at the bottom. Render starts pulling the
image, attaching the disk, and starting the container. The deploy takes
about 2–3 minutes for the first time (image pull + initial filesystem
setup).Watch the Logs tab — you should eventually see:
Render gives your service a public URL like https://comis-XXXX.onrender.com.
Find it on the service’s dashboard page (top of the screen, next to the green
“Live” badge).Run these from any terminal — they need no Docker or Comis CLI locally:
# Replace with your actual values:RENDER_URL=https://comis-XXXX.onrender.comGATEWAY_TOKEN=<the one you generated with openssl rand -hex 32># 1. Health check (no auth needed)curl -sf $RENDER_URL/health# → {"status":"ok","timestamp":"...","instanceId":"..."}# 2. Auth check — should return 401curl -s -o /dev/null -w "%{http_code}\n" $RENDER_URL/v1/chat/completions \ -X POST -H "Content-Type: application/json" \ -d '{"model":"default","messages":[{"role":"user","content":"hi"}]}'# → 401# 3. Real chat round-tripcurl -sS -X POST $RENDER_URL/v1/chat/completions \ -H "Authorization: Bearer $GATEWAY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "default", "messages": [{"role":"user","content":"Reply with exactly: hello-from-render"}] }' | jq -r '.choices[0].message.content'# → hello-from-render
If all three of those work, you have a fully functional Comis deployment.
Edit configuration through the UI (writes to /home/comis/.comis/config.yaml
on the persistent disk)
Bringing an existing config from local testing. If you’ve been testing
Comis locally, you already have a ~/.comis/config.yaml with agents and
channels configured. The simplest way to seed your Render deployment with
that config is via Render’s Secret Files
feature — see below.
If you’d rather declare the deployment in code (so you can recreate it from
scratch, version it, or share it with teammates), drop this render.yaml at
the root of any GitHub repo and link Render to it. Render watches the file
and applies changes automatically.
# render.yaml — Infrastructure as Code for a Comis deploymentservices: - type: web name: comis runtime: image image: url: docker.io/comisai/comis:1.0.42-slim # pin to an immutable tag plan: starter # $7/mo, has persistent disk region: oregon # closest to your users healthCheckPath: /health autoDeploy: false # don't auto-redeploy on every image push envVars: # Required networking — let the container listen on all interfaces # so Render's load balancer can route to it - key: COMIS_GATEWAY_HOST value: 0.0.0.0 - key: COMIS_GATEWAY_PORT value: 4766 # Auto-generated — Render creates a strong random value at first deploy # and stores it; the same value is reused on subsequent deploys - key: COMIS_GATEWAY_TOKEN generateValue: true # User must set these in the Render dashboard after first deploy # (sync: false means "don't store this value in render.yaml") - key: ANTHROPIC_API_KEY sync: false - key: TELEGRAM_BOT_TOKEN sync: false - key: DISCORD_BOT_TOKEN sync: false - key: SLACK_BOT_TOKEN sync: false disk: name: comis-data mountPath: /home/comis/.comis sizeGB: 1
If you have an existing config.yaml (e.g. from a local install) and want to
seed it on Render rather than configure through the dashboard:
1
Open Render dashboard for your service
Go to your service’s page → Environment tab in the left sidebar.
2
Add a Secret File
Scroll to Secret Files → click Add Secret File. Enter:
Field
Value
Filename
/etc/comis/config.yaml
File Contents
Paste your full local config.yaml
Click Save.
3
Patch the config for the container
Two changes are needed in the version you paste, compared to a typical
local-install config:1. Remove any dataDir: line at the top. Local configs from the
install wizard usually have dataDir: /Users/yourname/.comis (a host
absolute path that doesn’t exist inside the container). The image already
sets COMIS_DATA_DIR=/home/comis/.comis and the persistent disk mounts
there — drop the line and the daemon does the right thing.2. Set gateway.host: 0.0.0.0 (a typical install has 127.0.0.1,
which means “loopback only” — Render’s load balancer can’t reach it).Example before / after:
On Render → your service → Settings → scroll to Image URL → change
comisai/comis:1.0.42-slim to the new version → Save Changes.(Or, if you used render.yaml, edit the file in your repo and push — Render
picks up the change.)
3
Render redeploys automatically
The service goes through a brief downtime (~30s) while Render pulls the
new image, runs the health check, and switches traffic over. Your
persistent disk is reattached unchanged, so all memory and config carry over.
If you’re on latest-slim instead, click Manual Deploy → Deploy latest commit.
Render gives every service a *.onrender.com URL with HTTPS for free. To use
your own domain:
1
Add the domain in Render
Service page → Settings → Custom Domains → Add Custom Domain →
enter your domain (e.g. comis.example.com). Render shows you a CNAME
target.
2
Set the DNS record
At your DNS provider, create a CNAME record pointing
comis.example.com → the Render-provided target. Wait for DNS to
propagate (usually a few minutes).
3
Render provisions TLS
Render auto-issues a Let’s Encrypt cert once the CNAME resolves. The
custom-domains panel on Render shows the cert status.
No Comis-side configuration is needed for custom domains. Render terminates
TLS at the edge and forwards plain HTTP to your container on port 4766.
Most personal Comis deployments run comfortably on Starter — the daemon’s
baseline memory is around 200–400 MB and CPU is low except during LLM calls.The bulk of your spend is LLM API costs, not Render. A medium-traffic
single-user agent typically runs 0.50–5/mo in Anthropic charges
(see Context Engine & Cost Optimization
for cache savings) — much more than Render’s $7.
Open the Logs tab and look near the start of the most recent boot.
Most common causes:
FATAL: Bootstrap failed: Config validation failed: gateway.tokens.0.secret: Too small
You provided a COMIS_GATEWAY_TOKEN shorter than 32 characters. Generate
a fresh one with openssl rand -hex 32 (which produces 64 hex chars,
well over the 32-char minimum) and update the env var in Render.
FATAL: EACCES: permission denied, mkdir '/Users/...'
Your config.yaml has a dataDir: line pointing at a host absolute path
from a local machine. Remove that line — see
Patch the config for the container.
Health check timing out
Render’s default health check timeout is 30s, and Comis’s first boot
can take 10–25s while it initializes the SQLite databases on the
newly-attached disk. If it consistently times out, bump the health-check
timeout in service settings.
`/v1/chat/completions` returns 502 Bad Gateway
Render’s load balancer returns 502 when it can’t reach your container.
Almost always: the gateway is bound to 127.0.0.1 (loopback inside the
container) instead of 0.0.0.0 (all interfaces).Fix: Verify both of these are set:
Env var COMIS_GATEWAY_HOST=0.0.0.0 (Settings → Environment)
Your config.yaml (if you uploaded one) has gateway.host: 0.0.0.0 —
not 127.0.0.1. Config-file value wins over env var.
After fixing, click Manual Deploy → Deploy latest commit to restart.
The agent loses memory after every redeploy
You forgot to add the persistent disk, or its mount path is wrong.Fix: Service → Settings → scroll to Disks → confirm:
There is a disk attached
Mount Path is exactly /home/comis/.comis (this is the path the daemon
reads — anything else gets ignored)
If you previously deployed without the disk and lost data, that’s
unrecoverable — but adding the disk now will preserve everything from
this point forward.
Can the agent use the `exec` skill on Render?
Yes. Render runs containers on real Linux kernels, so the bubblewrap
exec sandbox is fully active — you’ll see
"Exec sandbox provider detected" provider=bwrap at startup, with no
Exec sandbox DISABLED warning.This is the major difference from Docker Desktop on macOS, where the
linuxkit kernel forces the daemon into auto-disabled-sandbox mode. On
Render, agent-issued shell commands run in a per-command sandbox that
excludes /home/comis/.comis from its mount set — prompt-injected exec
cannot read your .env, secrets.db, or config tokens.
Logs aren't showing anything I expect
Render’s Logs tab shows everything the container writes to stdout,
which is full Pino JSON. To make it readable in your browser, use Render’s
built-in filter box at the top of the tab — for example, type
"level":50 to see only ERROR-level lines.For richer log analysis (cost summaries, cache hit rates, request
timings), download the logs and run them through jq:
# Download via Render API or copy from the dashboardcat render-logs.txt | grep '^{' | jq -c 'select(.msg=="Execution complete")'
The structured fields include durationMs, tokensIn, tokensOut,
cacheHitRate, costUsd per LLM call.
My Telegram bot isn't responding
Common causes, in order of likelihood:
TELEGRAM_BOT_TOKEN env var not set — the daemon logs
"Channel telegram disabled" at startup if the token is missing or
invalid. Add the var, redeploy.
config.yaml doesn’t have channels.telegram.enabled: true —
even if the token is set, the channel must be enabled in config.
Telegram bot username collision / wrong token — easy to mix up
tokens between bots if you have several. Test by hitting
https://api.telegram.org/bot<YOUR_TOKEN>/getMe — that endpoint returns
the bot’s identity, so you can confirm the token belongs to the bot
you expect.
allowFrom filtering — your config may have channels.telegram.allowFrom
set to a Telegram user ID that doesn’t include the sender. The
daemon logs the rejected sender at DEBUG level — search the logs for
not in allowFrom.
How do I `exec` into the running container for debugging?
Render → service page → Shell tab in the left sidebar. This opens an
interactive shell inside the running container, running as the comis
user. From there you can:
# Inspect the daemon's data dirls -la /home/comis/.comis# Tail logs from inside the containertail -f /home/comis/.comis/logs/daemon.1.log# Inspect memory.dbsqlite3 /home/comis/.comis/memory.db .tables# Check what env vars the daemon seescat /proc/1/environ | tr '\0' '\n' | grep -E '^(COMIS_|ANTHROPIC|TELEGRAM)'
The shell is read-write on the persistent disk, so be careful — anything
you change here lasts across deploys.
How do I rotate the gateway token?
Generate a new one: openssl rand -hex 32
Render → service → Environment → update COMIS_GATEWAY_TOKEN
with the new value
Click Save Changes — Render auto-restarts the container
Update any clients (the dashboard prompt, your scripts, etc.) to use
the new token
The old token stops working immediately on restart. There’s no overlap
window — for zero-downtime token rotation you’d need two tokens
configured simultaneously (gateway.tokens is an array in config.yaml),
use the new one in clients, then remove the old entry.