From 9c5f9099f3194a59f16ee23f96bc584720a24c2e Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 13 May 2026 20:42:35 +0100 Subject: [PATCH 01/10] feat(deploy): add fly.io / Railway / Render one-click templates Closes #343. Lets operators stand up agentmemory on managed infrastructure without rolling their own Docker host. Each template extends the published rohitghumare64/agentmemory:latest image, mounts persistent storage at /data, generates the HMAC secret on first boot via openssl rand into /data/.hmac (chmod 600, printed once to stdout), and ships AGENTMEMORY_REQUIRE_HTTPS=1 by default so the v0.9.12 plaintext-bearer guard refuses to leak tokens over non-loopback HTTP if a TLS upstream gets misconfigured. Only port 3111 (REST API) is exposed publicly. The viewer on 3113 stays bound to localhost inside the container; every README documents the SSH-tunnel pattern. The main README gains a Deploy section with three Deploy-to-platform shield buttons linking to the platform's URL-encoded one-click flow, plus the deploy/ subtree with per-platform docs covering HMAC capture, rotation, /data backup, viewer access, cost floor, and known caveats. --- README.md | 30 +++++++++ deploy/README.md | 46 +++++++++++++ deploy/fly/Dockerfile | 14 ++++ deploy/fly/README.md | 111 +++++++++++++++++++++++++++++++ deploy/fly/entrypoint.sh | 37 +++++++++++ deploy/fly/fly.toml | 49 ++++++++++++++ deploy/railway/Dockerfile | 14 ++++ deploy/railway/README.md | 123 +++++++++++++++++++++++++++++++++++ deploy/railway/entrypoint.sh | 37 +++++++++++ deploy/railway/railway.json | 14 ++++ deploy/render/Dockerfile | 14 ++++ deploy/render/README.md | 106 ++++++++++++++++++++++++++++++ deploy/render/entrypoint.sh | 37 +++++++++++ deploy/render/render.yaml | 20 ++++++ 14 files changed, 652 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/fly/Dockerfile create mode 100644 deploy/fly/README.md create mode 100755 deploy/fly/entrypoint.sh create mode 100644 deploy/fly/fly.toml create mode 100644 deploy/railway/Dockerfile create mode 100644 deploy/railway/README.md create mode 100755 deploy/railway/entrypoint.sh create mode 100644 deploy/railway/railway.json create mode 100644 deploy/render/Dockerfile create mode 100644 deploy/render/README.md create mode 100755 deploy/render/entrypoint.sh create mode 100644 deploy/render/render.yaml diff --git a/README.md b/README.md index 4f330c5d..529caa6d 100644 --- a/README.md +++ b/README.md @@ -529,6 +529,36 @@ npx -y @agentmemory/mcp --- +

Deploy

+ +One-click templates for managed hosts. Each one extends the published +`rohitghumare64/agentmemory:latest` image, mounts persistent storage at +`/data`, generates an HMAC secret on first boot, and ships +`AGENTMEMORY_REQUIRE_HTTPS=1` by default so bearer-auth integrations +refuse to send tokens over plaintext HTTP to a non-loopback host. + +

+ Deploy to fly.io + Deploy to Railway + Deploy to Render +

+ +Full setup details (HMAC capture, viewer SSH tunnel, rotation, backup, +cost floors) live in [`deploy/`](./deploy/README.md): + +- [`deploy/fly`](./deploy/fly/README.md) — single machine with + `auto_stop_machines = "stop"`; cheapest idle. +- [`deploy/railway`](./deploy/railway/README.md) — Hobby plan flat fee, + volume in the dashboard. +- [`deploy/render`](./deploy/render/README.md) — Blueprint flow, + automatic disk snapshots on paid plans. + +Only port `3111` is published. The viewer on `3113` stays bound to +loopback inside the container — every template's README documents the +SSH-tunnel pattern for reaching it. + +--- +

Why agentmemory

Every coding agent forgets everything when the session ends. You waste the first 5 minutes of every session re-explaining your stack. agentmemory runs in the background and eliminates that entirely. diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 00000000..7fbddd8c --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,46 @@ +# One-click deploy templates + +Stand up agentmemory on managed infrastructure without rolling your own +Docker host. Each template extends the published image +`rohitghumare64/agentmemory:latest`, mounts persistent storage at +`/data`, generates an HMAC secret on first boot, and defaults +`AGENTMEMORY_REQUIRE_HTTPS=1` so the v0.9.12 plaintext-bearer guard +fires loud on any non-loopback misconfiguration. + +| Platform | Pitch | Cost floor | +|----------|-------|------------| +| [fly.io](./fly/README.md) | Single machine with auto-stop. Cheapest idle cost; cold-start on first request after sleep. | ~$0.15/month at full idle | +| [Railway](./railway/README.md) | Push from GitHub, volume in the dashboard. Easiest dashboard flow. | $5/month (Hobby plan flat fee) | +| [Render](./render/README.md) | Blueprint-driven; persistent disk attaches automatically. Most "set it and forget it." | $7.25/month (Starter web + 1 GB disk) | + +## What every template guarantees + +- **Volume mounted at `/data`.** Matches the path the distroless + agentmemory image has used since v0.9.10. +- **HMAC secret generated on first boot** via `openssl rand -hex 32`, + written to `/data/.hmac` with `chmod 600`, and printed to stdout + exactly once so the operator can capture it from the deploy logs. + Subsequent boots load the secret from the file. The secret is never + committed to a config file or set as a platform env var. +- **Only port 3111 is exposed publicly.** The viewer on port 3113 + stays bound to the container's localhost. Reach it via SSH tunnel + (see each platform's README). +- **`AGENTMEMORY_REQUIRE_HTTPS=1`** baked in. Integration clients + (Hermes, OpenClaw, pi) will refuse to send a bearer token over + plaintext HTTP to a non-loopback host — if a TLS termination + upstream gets misconfigured, the client fails loud instead of + silently leaking the secret. + +## Pick a platform + +- Pick **fly.io** if you want the lowest idle cost and don't mind a + cold-start latency hit on the first request after sleep. +- Pick **Railway** if you want a clicky dashboard flow and a flat + monthly bill. +- Pick **Render** if you want the most "set it and forget it" + Blueprint flow with automatic disk snapshots on paid plans. + +All three give you the same agentmemory API at the same port (3111) +with the same auth model. Migrating between them later is a `tar` of +`/data` and a re-import — see each platform's README for the exact +commands. diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile new file mode 100644 index 00000000..c50589b7 --- /dev/null +++ b/deploy/fly/Dockerfile @@ -0,0 +1,14 @@ +FROM rohitghumare64/agentmemory:latest + +ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ + AGENTMEMORY_HMAC_FILE=/data/.hmac \ + AGENTMEMORY_DATA_DIR=/data + +COPY entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh + +USER root +RUN chmod +x /usr/local/bin/agentmemory-entrypoint.sh + +EXPOSE 3111 + +ENTRYPOINT ["/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/fly/README.md b/deploy/fly/README.md new file mode 100644 index 00000000..c35dc16e --- /dev/null +++ b/deploy/fly/README.md @@ -0,0 +1,111 @@ +# Deploy agentmemory on fly.io + +This template runs agentmemory on a single fly.io machine with a 1 GB +persistent volume mounted at `/data`. The HMAC secret is generated on +first boot and persisted to the volume — you capture it from the deploy +logs exactly once. + +## What you get + +- A public HTTPS endpoint serving the agentmemory REST API on port 3111 +- A 1 GB Fly Volume at `/data` for memories, BM25 index, and stream backlog +- `auto_stop_machines = "stop"` and `min_machines_running = 0` — the + machine sleeps when idle, so cost floor approaches $0 for low traffic +- HTTP healthcheck at `/agentmemory/livez` every 30 s +- `AGENTMEMORY_REQUIRE_HTTPS=1` by default — clients that try to send + the bearer token over plaintext HTTP will refuse, matching the + v0.9.12 guard + +## One-time setup + +```bash +# 1. Install flyctl: https://fly.io/docs/flyctl/install/ +# 2. From this directory: +fly launch --copy-config --no-deploy --name agentmemory + +# 3. Create the volume in the same region as the app: +fly volumes create agentmemory_data --region iad --size 1 + +# 4. Deploy: +fly deploy +``` + +## Capture the HMAC secret + +Right after the first deploy succeeds: + +```bash +fly logs --app agentmemory | grep -A1 AGENTMEMORY_SECRET= +``` + +You will see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. +Copy it into your client environment (`~/.bashrc`, Claude Desktop config, +etc.). The secret is never printed again on subsequent boots. + +## Verify the deployment + +```bash +curl https://agentmemory.fly.dev/agentmemory/livez +# {"status":"ok"} +``` + +For an authenticated call, your client must send `Authorization: Bearer `. + +## Viewer access (port 3113 stays internal) + +The viewer port is intentionally not exposed publicly. Tunnel to it: + +```bash +fly proxy 3113:3113 --app agentmemory +# then open http://localhost:3113 +``` + +`fly proxy` opens an mTLS WireGuard channel to the machine, so the +viewer's bearer token still has to ride a loopback connection on your +laptop — the v0.9.12 plaintext-bearer guard stays satisfied. + +## Rotate the HMAC secret + +```bash +fly ssh console --app agentmemory +rm /data/.hmac +exit +fly machine restart +fly logs --app agentmemory | grep AGENTMEMORY_SECRET= +``` + +Update every client with the new secret. Old tokens stop working +immediately. + +## Back up `/data` + +```bash +fly ssh console --app agentmemory -C "tar czf - /data" > agentmemory-$(date +%Y%m%d).tar.gz +``` + +To restore on a fresh machine: + +```bash +cat agentmemory-YYYYMMDD.tar.gz | fly ssh console --app agentmemory -C "tar xzf - -C /" +fly machine restart +``` + +## Cost floor and egress + +- Idle (machine stopped): the volume costs ~$0.15/GB/month. A 1 GB + volume is roughly $0.15/month. +- Active (machine running on `shared-cpu-1x` with 512 MB): about + $1.94/month if it ran 24/7; in practice `auto_stop_machines` keeps + that well under $1. +- Outbound bandwidth: 100 GB/month free on the Hobby plan, then $0.02/GB + in North America / Europe. + +See for the up-to-date rate card. + +## Known caveats + +- The volume lives in one region. To survive a region outage, create a + second volume in another region and update `primary_region` after the + failover, or take snapshots with `fly volumes snapshots create`. +- The first deploy needs a published `rohitghumare64/agentmemory:latest` + image. Multi-arch (amd64 + arm64) so it works on either fly.io VM type. diff --git a/deploy/fly/entrypoint.sh b/deploy/fly/entrypoint.sh new file mode 100755 index 00000000..2249abce --- /dev/null +++ b/deploy/fly/entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# agentmemory first-boot entrypoint. +# +# On first boot, generates a 256-bit HMAC secret with openssl rand, +# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), +# and prints it to stdout exactly once so the operator can capture it +# from the platform's deploy logs. On subsequent boots the file already +# exists and we just load it. +# +# To rotate: delete the file and restart the machine. The next boot +# will print a fresh secret. + +set -eu + +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" + +mkdir -p "${DATA_DIR}" + +if [ ! -s "${HMAC_FILE}" ]; then + SECRET="$(openssl rand -hex 32)" + umask 077 + printf '%s\n' "${SECRET}" > "${HMAC_FILE}" + chmod 600 "${HMAC_FILE}" + echo "================================================================" + echo "agentmemory: generated HMAC secret on first boot" + echo "AGENTMEMORY_SECRET=${SECRET}" + echo "Copy this value now. It will not be printed again." + echo "Stored at: ${HMAC_FILE} (chmod 600)" + echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "================================================================" +fi + +AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +export AGENTMEMORY_SECRET + +exec agentmemory "$@" diff --git a/deploy/fly/fly.toml b/deploy/fly/fly.toml new file mode 100644 index 00000000..775adf4e --- /dev/null +++ b/deploy/fly/fly.toml @@ -0,0 +1,49 @@ +# fly.io deployment for agentmemory. +# +# The HMAC secret is generated by entrypoint.sh on first boot and persisted +# to the mounted volume at /data/.hmac. Operator copies it once from +# `fly logs` then never sees it again. To rotate: `fly ssh console` and +# `rm /data/.hmac`, then `fly machine restart`. +# +# Only port 3111 (REST API) is exposed publicly. Viewer 3113 stays bound +# to localhost inside the machine; reach it via `fly proxy 3113:3113`. + +app = "agentmemory" +primary_region = "iad" + +[build] + dockerfile = "Dockerfile" + +[[mounts]] + source = "agentmemory_data" + destination = "/data" + initial_size = "1gb" + +[http_service] + internal_port = 3111 + force_https = true + auto_stop_machines = "stop" + auto_start_machines = true + min_machines_running = 0 + processes = ["app"] + + [http_service.concurrency] + type = "requests" + soft_limit = 200 + hard_limit = 250 + + [[http_service.checks]] + interval = "30s" + timeout = "5s" + grace_period = "10s" + method = "GET" + path = "/agentmemory/livez" + +[env] + AGENTMEMORY_REQUIRE_HTTPS = "1" + AGENTMEMORY_HMAC_FILE = "/data/.hmac" + AGENTMEMORY_DATA_DIR = "/data" + +[[vm]] + size = "shared-cpu-1x" + memory = "512mb" diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile new file mode 100644 index 00000000..c50589b7 --- /dev/null +++ b/deploy/railway/Dockerfile @@ -0,0 +1,14 @@ +FROM rohitghumare64/agentmemory:latest + +ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ + AGENTMEMORY_HMAC_FILE=/data/.hmac \ + AGENTMEMORY_DATA_DIR=/data + +COPY entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh + +USER root +RUN chmod +x /usr/local/bin/agentmemory-entrypoint.sh + +EXPOSE 3111 + +ENTRYPOINT ["/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/railway/README.md b/deploy/railway/README.md new file mode 100644 index 00000000..0e1b4b31 --- /dev/null +++ b/deploy/railway/README.md @@ -0,0 +1,123 @@ +# Deploy agentmemory on Railway + +This template runs agentmemory on a single Railway service with a +persistent volume mounted at `/data`. The HMAC secret is generated on +first boot and persisted to the volume — you read it once from the +deploy logs and copy it into your client. + +## What you get + +- A public HTTPS endpoint serving the agentmemory REST API on port 3111 +- A persistent Railway Volume at `/data` for memories, BM25 index, and + stream backlog +- Railway healthcheck against `/agentmemory/livez` +- `AGENTMEMORY_REQUIRE_HTTPS=1` by default — clients that try to send + the bearer token over plaintext HTTP refuse, matching the v0.9.12 + guard + +## Deploy via Railway dashboard + +1. Click **Deploy from GitHub** in the Railway dashboard and pick the + `rohitg00/agentmemory` repo. +2. Set the **Config-as-Code Path** under the service Settings to + `deploy/railway/railway.json`. Railway picks up the Dockerfile path + from there. +3. Open the service's **Volumes** tab and add a volume mounted at + `/data` (Railway volumes are configured in the dashboard or via + `railway volume add`, not in `railway.json`). +4. Click **Deploy**. + +## Deploy via Railway CLI + +```bash +# Install: https://docs.railway.com/guides/cli +railway login +railway init # link a new project +railway up --service agentmemory # builds + deploys +railway volume add --service agentmemory --mount /data # attach persistent volume +railway redeploy # restart with the volume +``` + +## Capture the HMAC secret + +After the first deploy succeeds, open the service's **Deploy Logs**: + +```bash +railway logs --service agentmemory | grep AGENTMEMORY_SECRET= +``` + +You will see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. +Copy it into your client environment. The secret is never printed again +on subsequent boots. + +## Verify the deployment + +```bash +curl https://.up.railway.app/agentmemory/livez +# {"status":"ok"} +``` + +For an authenticated call, your client must send `Authorization: Bearer `. + +## Viewer access (port 3113 stays internal) + +Railway only exposes the single public port from your service's +`PORT` env var (which we map to 3111). The viewer stays bound to +localhost inside the container. To reach it, use `railway ssh`: + +```bash +railway ssh --service agentmemory +# inside the container: +curl http://localhost:3113 +``` + +For a usable browser session, run a local proxy through `railway ssh`: + +```bash +railway ssh --service agentmemory -- -L 3113:localhost:3113 +# now http://localhost:3113 in your browser hits the in-container viewer +``` + +## Rotate the HMAC secret + +```bash +railway ssh --service agentmemory +rm /data/.hmac +exit +railway redeploy --service agentmemory +railway logs --service agentmemory | grep AGENTMEMORY_SECRET= +``` + +Update every client with the new secret. Old tokens stop working +immediately. + +## Back up `/data` + +```bash +railway ssh --service agentmemory -- "tar czf - /data" > agentmemory-$(date +%Y%m%d).tar.gz +``` + +To restore on a fresh volume: + +```bash +cat agentmemory-YYYYMMDD.tar.gz | railway ssh --service agentmemory -- "tar xzf - -C /" +railway redeploy --service agentmemory +``` + +## Cost floor and egress + +- Hobby plan: $5/month flat, includes $5 of usage. +- agentmemory at idle plus a 1 GB volume typically uses $3–$6 of usage + per month on the smallest instance, so most users stay near the $5 + floor. +- Egress: $0.10/GB after the bundled allowance. + +See for the current rate card. + +## Known caveats + +- Railway volumes do not auto-snapshot. Take your own backups (above) + or use the dashboard's manual snapshot feature. +- The published image `rohitghumare64/agentmemory:latest` should be + multi-arch (amd64 + arm64). Railway currently runs amd64 only, so + amd64 must be present. diff --git a/deploy/railway/entrypoint.sh b/deploy/railway/entrypoint.sh new file mode 100755 index 00000000..faf66a82 --- /dev/null +++ b/deploy/railway/entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# agentmemory first-boot entrypoint. +# +# On first boot, generates a 256-bit HMAC secret with openssl rand, +# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), +# and prints it to stdout exactly once so the operator can capture it +# from the platform's deploy logs. On subsequent boots the file already +# exists and we just load it. +# +# To rotate: delete the file and restart the service. The next boot +# will print a fresh secret. + +set -eu + +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" + +mkdir -p "${DATA_DIR}" + +if [ ! -s "${HMAC_FILE}" ]; then + SECRET="$(openssl rand -hex 32)" + umask 077 + printf '%s\n' "${SECRET}" > "${HMAC_FILE}" + chmod 600 "${HMAC_FILE}" + echo "================================================================" + echo "agentmemory: generated HMAC secret on first boot" + echo "AGENTMEMORY_SECRET=${SECRET}" + echo "Copy this value now. It will not be printed again." + echo "Stored at: ${HMAC_FILE} (chmod 600)" + echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "================================================================" +fi + +AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +export AGENTMEMORY_SECRET + +exec agentmemory "$@" diff --git a/deploy/railway/railway.json b/deploy/railway/railway.json new file mode 100644 index 00000000..87270d52 --- /dev/null +++ b/deploy/railway/railway.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://railway.com/railway.schema.json", + "build": { + "builder": "DOCKERFILE", + "dockerfilePath": "deploy/railway/Dockerfile" + }, + "deploy": { + "numReplicas": 1, + "healthcheckPath": "/agentmemory/livez", + "healthcheckTimeout": 30, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 10 + } +} diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile new file mode 100644 index 00000000..c50589b7 --- /dev/null +++ b/deploy/render/Dockerfile @@ -0,0 +1,14 @@ +FROM rohitghumare64/agentmemory:latest + +ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ + AGENTMEMORY_HMAC_FILE=/data/.hmac \ + AGENTMEMORY_DATA_DIR=/data + +COPY entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh + +USER root +RUN chmod +x /usr/local/bin/agentmemory-entrypoint.sh + +EXPOSE 3111 + +ENTRYPOINT ["/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/render/README.md b/deploy/render/README.md new file mode 100644 index 00000000..9dc1e629 --- /dev/null +++ b/deploy/render/README.md @@ -0,0 +1,106 @@ +# Deploy agentmemory on Render + +This template runs agentmemory on a single Render Web Service with a +persistent disk mounted at `/data`. The HMAC secret is generated on +first boot and persisted to the disk — you capture it from the deploy +logs exactly once. + +## What you get + +- A public HTTPS endpoint serving the agentmemory REST API on port 3111 +- A 1 GB persistent disk at `/data` for memories, BM25 index, and + stream backlog +- Render healthcheck against `/agentmemory/livez` +- `AGENTMEMORY_REQUIRE_HTTPS=1` by default — clients that try to send + the bearer token over plaintext HTTP refuse, matching the v0.9.12 + guard + +## Deploy via Render Blueprint + +1. Push the `deploy/render/` directory to a Git provider Render can + reach (a fork of `rohitg00/agentmemory` works). +2. In the Render dashboard, click **New +** → **Blueprint**. +3. Point Render at the repo and the path `deploy/render/render.yaml`. +4. Render reads the Blueprint, provisions the disk, builds the + Dockerfile, and starts the service. The whole flow takes 3–5 + minutes on the first run. + +## Deploy via Render Deploy Hook (one-click) + +Once the Blueprint exists in your account, generate a Deploy Hook URL +in the service settings. Future deploys are a single curl call: + +```bash +curl "https://api.render.com/deploy/srv-XXYYZZ?key=AABBCC" +``` + +To roll out a specific image tag rather than rebuilding from the +Dockerfile, append `&imgURL=docker.io%2Frohitghumare64%2Fagentmemory%3A`. + +## Capture the HMAC secret + +After the first deploy succeeds, open the service's **Logs** tab and +search for `AGENTMEMORY_SECRET=`. You will see exactly one line of the +form `AGENTMEMORY_SECRET=<64 hex chars>`. Copy it into your client +environment. The secret is never printed again on subsequent boots. + +## Verify the deployment + +```bash +curl https://agentmemory.onrender.com/agentmemory/livez +# {"status":"ok"} +``` + +For an authenticated call, your client must send `Authorization: Bearer `. + +## Viewer access (port 3113 stays internal) + +Render only exposes one public port per service, and we use it for +3111. The viewer port stays bound to localhost inside the container. +Reach it via Render's SSH: + +```bash +# Settings → SSH → enable for your service, copy the connection command +ssh srv-XXYYZZ@ssh..render.com -L 3113:localhost:3113 +# now http://localhost:3113 in your browser hits the in-container viewer +``` + +## Rotate the HMAC secret + +```bash +ssh srv-XXYYZZ@ssh..render.com +rm /data/.hmac +exit +# trigger a redeploy from the Render dashboard or via the Deploy Hook +``` + +After the redeploy, grab the new secret from the logs and update every +client. Old tokens stop working immediately. + +## Back up `/data` + +```bash +ssh srv-XXYYZZ@ssh..render.com "tar czf - /data" > agentmemory-$(date +%Y%m%d).tar.gz +``` + +Render also takes daily snapshots of persistent disks automatically on +paid plans — the SSH tarball is a belt-and-braces option you can ship +off-platform. + +## Cost floor and egress + +- Starter plan web service: $7/month (0.5 CPU, 512 MB RAM). +- 1 GB persistent disk: $0.25/GB/month, so $0.25/month for the default. +- Bandwidth: 100 GB outbound included, then $0.10/GB. + +See for the current rate card. + +## Known caveats + +- Render Free tier does not support persistent disks. The Starter plan + ($7/month) is the minimum. +- Render restarts the service on every deploy. The HMAC secret survives + because it lives on the disk, but expect a 10–30 s gap of 502s + during rollouts. +- The published image `rohitghumare64/agentmemory:latest` must include + amd64 (Render does not run arm64 web services as of writing). diff --git a/deploy/render/entrypoint.sh b/deploy/render/entrypoint.sh new file mode 100755 index 00000000..faf66a82 --- /dev/null +++ b/deploy/render/entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# agentmemory first-boot entrypoint. +# +# On first boot, generates a 256-bit HMAC secret with openssl rand, +# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), +# and prints it to stdout exactly once so the operator can capture it +# from the platform's deploy logs. On subsequent boots the file already +# exists and we just load it. +# +# To rotate: delete the file and restart the service. The next boot +# will print a fresh secret. + +set -eu + +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" + +mkdir -p "${DATA_DIR}" + +if [ ! -s "${HMAC_FILE}" ]; then + SECRET="$(openssl rand -hex 32)" + umask 077 + printf '%s\n' "${SECRET}" > "${HMAC_FILE}" + chmod 600 "${HMAC_FILE}" + echo "================================================================" + echo "agentmemory: generated HMAC secret on first boot" + echo "AGENTMEMORY_SECRET=${SECRET}" + echo "Copy this value now. It will not be printed again." + echo "Stored at: ${HMAC_FILE} (chmod 600)" + echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "================================================================" +fi + +AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +export AGENTMEMORY_SECRET + +exec agentmemory "$@" diff --git a/deploy/render/render.yaml b/deploy/render/render.yaml new file mode 100644 index 00000000..986f1a46 --- /dev/null +++ b/deploy/render/render.yaml @@ -0,0 +1,20 @@ +services: + - type: web + name: agentmemory + runtime: docker + plan: starter + dockerfilePath: ./deploy/render/Dockerfile + dockerContext: ./deploy/render + healthCheckPath: /agentmemory/livez + autoDeploy: false + disk: + name: data + mountPath: /data + sizeGB: 1 + envVars: + - key: AGENTMEMORY_REQUIRE_HTTPS + value: "1" + - key: AGENTMEMORY_HMAC_FILE + value: /data/.hmac + - key: AGENTMEMORY_DATA_DIR + value: /data From 686558543ab5a53da468ce1905819f99231b7975 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 13 May 2026 21:58:09 +0100 Subject: [PATCH 02/10] fix(deploy): run as non-root, placeholder app names, drop invalid Render badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses CodeRabbit findings on PR #361. Dockerfiles (fly/railway/render): drop `USER root` + RUN chmod — those left the container running as root after build. Replaced with `COPY --chmod=0755 --chown=65532:65532` so the entrypoint script lands with the correct permissions and owner without ever switching off the distroless image's default nonroot user. Added an explicit `USER 65532:65532` directive after COPY to make the runtime user intent-visible to reviewers. fly/README.md: every command referenced the hardcoded app name `agentmemory`. Since the global `agentmemory` slug on Fly is almost certainly taken, the first `fly launch --name agentmemory` fails and the rest of the volume/log commands point at a non-existent app. Walk users through setting `APP="agentmemory-$(whoami)"` once at the top and reference `$APP` everywhere (launch / volumes / logs / proxy / ssh / backup) — and derive the volume name (`agentmemory_data` style) from `$APP` so it stays consistent. railway/README.md: the previous `railway ssh --service agentmemory -- -L 3113:localhost:3113` snippet was invalid — Railway's CLI ssh does not support port forwarding. Replaced with a quick in-container curl check plus two valid browser paths: Railway TCP Proxy (the recommended option, dashboard → Networking → TCP Proxy on container port 3113) and an in-container sshd over a TCP Proxy if true SSH tunneling is needed. README.md: removed the "Deploy to Render" badge. Render's deploy URL requires `render.yaml` at the repository root, which we keep clean. The `deploy/render/` blueprint stays in-tree for users who set up the Render Blueprint flow manually — the deploy section now points readers there explicitly. Skipped: the entrypoint.sh SECRET-shape regex check. `openssl rand -hex 32` under `set -eu` already exits non-zero on any failure path and always emits exactly 64 hex characters when it succeeds — a post-hoc regex would only fire on a kernel-level surprise we can't recover from anyway. --- README.md | 3 ++- deploy/fly/Dockerfile | 5 ++--- deploy/fly/README.md | 37 ++++++++++++++++++++++++------------- deploy/railway/Dockerfile | 5 ++--- deploy/railway/README.md | 23 ++++++++++++++++------- deploy/render/Dockerfile | 5 ++--- 6 files changed, 48 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 529caa6d..1e208d1f 100644 --- a/README.md +++ b/README.md @@ -540,9 +540,10 @@ refuse to send tokens over plaintext HTTP to a non-loopback host.

Deploy to fly.io Deploy to Railway - Deploy to Render

+Render's one-click deploy button requires `render.yaml` at the repository root, which we deliberately keep clean. Use the Render Blueprint flow documented in [`deploy/render/`](./deploy/render/README.md) to point at the in-repo blueprint manually. + Full setup details (HMAC capture, viewer SSH tunnel, rotation, backup, cost floors) live in [`deploy/`](./deploy/README.md): diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile index c50589b7..e80119e7 100644 --- a/deploy/fly/Dockerfile +++ b/deploy/fly/Dockerfile @@ -4,10 +4,9 @@ ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data -COPY entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh +COPY --chmod=0755 --chown=65532:65532 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh -USER root -RUN chmod +x /usr/local/bin/agentmemory-entrypoint.sh +USER 65532:65532 EXPOSE 3111 diff --git a/deploy/fly/README.md b/deploy/fly/README.md index c35dc16e..57350c50 100644 --- a/deploy/fly/README.md +++ b/deploy/fly/README.md @@ -18,24 +18,35 @@ logs exactly once. ## One-time setup +Pick a unique Fly app name first — `agentmemory` itself is likely taken. +Every command below references `$APP`, so set it once and the rest of the +flow stays consistent: + ```bash # 1. Install flyctl: https://fly.io/docs/flyctl/install/ -# 2. From this directory: -fly launch --copy-config --no-deploy --name agentmemory +# 2. Pick your unique app name (and matching volume name): +export APP="agentmemory-$(whoami)" # or any other globally-unique name +export VOLUME="${APP//-/_}_data" # Fly volume names can't contain '-' + +# 3. From this directory: +fly launch --copy-config --no-deploy --name "$APP" -# 3. Create the volume in the same region as the app: -fly volumes create agentmemory_data --region iad --size 1 +# 4. Create the volume in the same region as the app: +fly volumes create "$VOLUME" --region iad --size 1 -# 4. Deploy: -fly deploy +# 5. Deploy: +fly deploy --app "$APP" ``` +If `fly launch` reports the name is taken, pick another value for `$APP`, +re-export, and re-run. + ## Capture the HMAC secret Right after the first deploy succeeds: ```bash -fly logs --app agentmemory | grep -A1 AGENTMEMORY_SECRET= +fly logs --app "$APP" | grep -A1 AGENTMEMORY_SECRET= ``` You will see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. @@ -45,7 +56,7 @@ etc.). The secret is never printed again on subsequent boots. ## Verify the deployment ```bash -curl https://agentmemory.fly.dev/agentmemory/livez +curl "https://$APP.fly.dev/agentmemory/livez" # {"status":"ok"} ``` @@ -56,7 +67,7 @@ For an authenticated call, your client must send `Authorization: Bearer The viewer port is intentionally not exposed publicly. Tunnel to it: ```bash -fly proxy 3113:3113 --app agentmemory +fly proxy 3113:3113 --app "$APP" # then open http://localhost:3113 ``` @@ -67,11 +78,11 @@ laptop — the v0.9.12 plaintext-bearer guard stays satisfied. ## Rotate the HMAC secret ```bash -fly ssh console --app agentmemory +fly ssh console --app "$APP" rm /data/.hmac exit fly machine restart -fly logs --app agentmemory | grep AGENTMEMORY_SECRET= +fly logs --app "$APP" | grep AGENTMEMORY_SECRET= ``` Update every client with the new secret. Old tokens stop working @@ -80,13 +91,13 @@ immediately. ## Back up `/data` ```bash -fly ssh console --app agentmemory -C "tar czf - /data" > agentmemory-$(date +%Y%m%d).tar.gz +fly ssh console --app "$APP" -C "tar czf - /data" > "$APP-$(date +%Y%m%d).tar.gz" ``` To restore on a fresh machine: ```bash -cat agentmemory-YYYYMMDD.tar.gz | fly ssh console --app agentmemory -C "tar xzf - -C /" +cat "$APP-YYYYMMDD.tar.gz" | fly ssh console --app "$APP" -C "tar xzf - -C /" fly machine restart ``` diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile index c50589b7..e80119e7 100644 --- a/deploy/railway/Dockerfile +++ b/deploy/railway/Dockerfile @@ -4,10 +4,9 @@ ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data -COPY entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh +COPY --chmod=0755 --chown=65532:65532 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh -USER root -RUN chmod +x /usr/local/bin/agentmemory-entrypoint.sh +USER 65532:65532 EXPOSE 3111 diff --git a/deploy/railway/README.md b/deploy/railway/README.md index 0e1b4b31..6a8c8fb7 100644 --- a/deploy/railway/README.md +++ b/deploy/railway/README.md @@ -63,7 +63,11 @@ For an authenticated call, your client must send `Authorization: Bearer Railway only exposes the single public port from your service's `PORT` env var (which we map to 3111). The viewer stays bound to -localhost inside the container. To reach it, use `railway ssh`: +localhost inside the container. `railway ssh` is an interactive shell +only — it does not support `-L`-style port forwarding, so reach the +viewer with one of the following. + +**Quick in-container check:** ```bash railway ssh --service agentmemory @@ -71,12 +75,17 @@ railway ssh --service agentmemory curl http://localhost:3113 ``` -For a usable browser session, run a local proxy through `railway ssh`: - -```bash -railway ssh --service agentmemory -- -L 3113:localhost:3113 -# now http://localhost:3113 in your browser hits the in-container viewer -``` +**Browser session — option A (TCP Proxy, recommended):** in the Railway +dashboard, open the service's *Settings → Networking* tab and add a +**TCP Proxy** for container port `3113`. Railway returns a public +host/port pair you can hit directly from your browser. Pair it with the +HMAC bearer-auth header so the viewer is not anonymously reachable. + +**Browser session — option B (in-container sshd):** add an `openssh-server` +process to the image and start it from `entrypoint.sh` on a fixed port, +expose that port through a second Railway TCP Proxy, then use a native +`ssh -L 3113:localhost:3113 -p ` from your laptop. +This is the heavier path; option A is what most users will want. ## Rotate the HMAC secret diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile index c50589b7..e80119e7 100644 --- a/deploy/render/Dockerfile +++ b/deploy/render/Dockerfile @@ -4,10 +4,9 @@ ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data -COPY entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh +COPY --chmod=0755 --chown=65532:65532 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh -USER root -RUN chmod +x /usr/local/bin/agentmemory-entrypoint.sh +USER 65532:65532 EXPOSE 3111 From 1dbcf19ef7b453feaa2462be6192fa5ef5db824c Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 13 May 2026 22:44:30 +0100 Subject: [PATCH 03/10] fix(deploy): self-contained Dockerfiles (no phantom base image) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Templates previously did `FROM rohitghumare64/agentmemory:latest`, an image that does not exist on Docker Hub. agentmemory ships via npm (`@agentmemory/agentmemory`) and runs against the `iii` engine binary fetched from the `iii-hq/iii` GitHub release. There is no first-party agentmemory Docker image yet. Rewrote all three Dockerfiles to be self-contained against `node:22-slim`: - `apt-get` curl / openssl / ca-certificates / tini for the runtime prereqs (openssl for the first-boot HMAC, tini for clean PID 1). - Download the iii binary from `github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/...` matching the build host's arch (uname -m). - `npm install -g @agentmemory/agentmemory@${AGENTMEMORY_VERSION}` with `--omit=optional` so the CJK native deps (added in PR #362) don't fail the build on platforms without musl/glibc prebuilds. - `mkdir -p /data && chown node:node /data` so the existing first-boot entrypoint can write `/data/.hmac` without root. - `USER node:node` (UID 1000, the standard non-root user in the node image) instead of the made-up `65532:65532` that assumed a distroless base. Both `AGENTMEMORY_VERSION` and `III_VERSION` are `ARG`s so platform operators can pin a specific release through their dashboard's build-args UI without editing the Dockerfile. README updates: - Top-level `README.md` and `deploy/README.md` no longer claim users pull a pre-published image; the wording now matches what each template actually does (build from source on the platform's builder). - Per-platform "Known caveats" sections drop the `rohitghumare64/agentmemory:latest` line and replace it with notes about build-time, cache reuse, and how to pin via build args. - `deploy/render/README.md` drops the obsolete `&imgURL=...` Deploy Hook query-string trick and replaces it with `AGENTMEMORY_VERSION` build-arg guidance. - `deploy/README.md` switches "Integration clients (Hermes, OpenClaw, pi)" → "Integration plugins" to match the scrub convention from the earlier PR-body edit. --- README.md | 12 +++++++----- deploy/README.md | 24 ++++++++++++------------ deploy/fly/Dockerfile | 29 +++++++++++++++++++++++++---- deploy/fly/README.md | 8 ++++++-- deploy/railway/Dockerfile | 29 +++++++++++++++++++++++++---- deploy/railway/README.md | 7 ++++--- deploy/render/Dockerfile | 29 +++++++++++++++++++++++++---- deploy/render/README.md | 9 +++++---- 8 files changed, 109 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 1e208d1f..d28b0add 100644 --- a/README.md +++ b/README.md @@ -531,11 +531,13 @@ npx -y @agentmemory/mcp

Deploy

-One-click templates for managed hosts. Each one extends the published -`rohitghumare64/agentmemory:latest` image, mounts persistent storage at -`/data`, generates an HMAC secret on first boot, and ships -`AGENTMEMORY_REQUIRE_HTTPS=1` by default so bearer-auth integrations -refuse to send tokens over plaintext HTTP to a non-loopback host. +One-click templates for managed hosts. Each one ships a self-contained +Dockerfile that pulls `@agentmemory/agentmemory` from npm and bundles +the `iii-engine` binary at build time — no pre-published image +required. Persistent storage mounts at `/data`, an HMAC secret is +generated on first boot, and `AGENTMEMORY_REQUIRE_HTTPS=1` is baked in +so bearer-auth integrations refuse to send tokens over plaintext HTTP +to a non-loopback host.

Deploy to fly.io diff --git a/deploy/README.md b/deploy/README.md index 7fbddd8c..52b7588b 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,11 +1,12 @@ # One-click deploy templates Stand up agentmemory on managed infrastructure without rolling your own -Docker host. Each template extends the published image -`rohitghumare64/agentmemory:latest`, mounts persistent storage at -`/data`, generates an HMAC secret on first boot, and defaults -`AGENTMEMORY_REQUIRE_HTTPS=1` so the v0.9.12 plaintext-bearer guard -fires loud on any non-loopback misconfiguration. +Docker host. Each template ships a self-contained Dockerfile that pulls +`@agentmemory/agentmemory` from npm at build time and bundles the +`iii-engine` binary alongside it — no pre-published image required. +Storage mounts at `/data`, an HMAC secret is generated on first boot, +and `AGENTMEMORY_REQUIRE_HTTPS=1` is baked in so the v0.9.12 +plaintext-bearer guard fires loud on any non-loopback misconfiguration. | Platform | Pitch | Cost floor | |----------|-------|------------| @@ -15,8 +16,8 @@ fires loud on any non-loopback misconfiguration. ## What every template guarantees -- **Volume mounted at `/data`.** Matches the path the distroless - agentmemory image has used since v0.9.10. +- **Volume mounted at `/data`.** Matches the path the engine has used + since v0.9.10. - **HMAC secret generated on first boot** via `openssl rand -hex 32`, written to `/data/.hmac` with `chmod 600`, and printed to stdout exactly once so the operator can capture it from the deploy logs. @@ -25,11 +26,10 @@ fires loud on any non-loopback misconfiguration. - **Only port 3111 is exposed publicly.** The viewer on port 3113 stays bound to the container's localhost. Reach it via SSH tunnel (see each platform's README). -- **`AGENTMEMORY_REQUIRE_HTTPS=1`** baked in. Integration clients - (Hermes, OpenClaw, pi) will refuse to send a bearer token over - plaintext HTTP to a non-loopback host — if a TLS termination - upstream gets misconfigured, the client fails loud instead of - silently leaking the secret. +- **`AGENTMEMORY_REQUIRE_HTTPS=1`** baked in. Integration plugins will + refuse to send a bearer token over plaintext HTTP to a non-loopback + host — if a TLS termination upstream gets misconfigured, the client + fails loud instead of silently leaking the secret. ## Pick a platform diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile index e80119e7..62d3c39d 100644 --- a/deploy/fly/Dockerfile +++ b/deploy/fly/Dockerfile @@ -1,13 +1,34 @@ -FROM rohitghumare64/agentmemory:latest +FROM node:22-slim + +ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data -COPY --chmod=0755 --chown=65532:65532 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && rm -rf /var/lib/apt/lists/* + +RUN ARCH=$(uname -m) \ + && case "$ARCH" in \ + x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ + aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ + *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ + esac \ + && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ + | tar -xz -C /usr/local/bin/ \ + && chmod +x /usr/local/bin/iii + +RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit + +RUN mkdir -p /data && chown -R node:node /data + +COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh -USER 65532:65532 +USER node:node EXPOSE 3111 -ENTRYPOINT ["/usr/local/bin/agentmemory-entrypoint.sh"] +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/fly/README.md b/deploy/fly/README.md index 57350c50..d78ee6b3 100644 --- a/deploy/fly/README.md +++ b/deploy/fly/README.md @@ -118,5 +118,9 @@ See for the up-to-date rate card. - The volume lives in one region. To survive a region outage, create a second volume in another region and update `primary_region` after the failover, or take snapshots with `fly volumes snapshots create`. -- The first deploy needs a published `rohitghumare64/agentmemory:latest` - image. Multi-arch (amd64 + arm64) so it works on either fly.io VM type. +- The Dockerfile builds in the Fly Builder on every deploy — first + deploy takes ~2 minutes while npm + the iii binary download. Cached + layers shrink subsequent rebuilds to under 30 seconds. +- Bump `AGENTMEMORY_VERSION` or `III_VERSION` in the Dockerfile to + upgrade. `fly deploy --build-arg AGENTMEMORY_VERSION=` also works + for a one-off without editing the file. diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile index e80119e7..62d3c39d 100644 --- a/deploy/railway/Dockerfile +++ b/deploy/railway/Dockerfile @@ -1,13 +1,34 @@ -FROM rohitghumare64/agentmemory:latest +FROM node:22-slim + +ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data -COPY --chmod=0755 --chown=65532:65532 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && rm -rf /var/lib/apt/lists/* + +RUN ARCH=$(uname -m) \ + && case "$ARCH" in \ + x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ + aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ + *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ + esac \ + && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ + | tar -xz -C /usr/local/bin/ \ + && chmod +x /usr/local/bin/iii + +RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit + +RUN mkdir -p /data && chown -R node:node /data + +COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh -USER 65532:65532 +USER node:node EXPOSE 3111 -ENTRYPOINT ["/usr/local/bin/agentmemory-entrypoint.sh"] +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/railway/README.md b/deploy/railway/README.md index 6a8c8fb7..0c5dd849 100644 --- a/deploy/railway/README.md +++ b/deploy/railway/README.md @@ -127,6 +127,7 @@ See for the current rate card. - Railway volumes do not auto-snapshot. Take your own backups (above) or use the dashboard's manual snapshot feature. -- The published image `rohitghumare64/agentmemory:latest` should be - multi-arch (amd64 + arm64). Railway currently runs amd64 only, so - amd64 must be present. +- The Dockerfile builds on Railway's builder on every deploy. First + deploy is ~2 minutes; cached layers make subsequent rebuilds quick. + Pin `AGENTMEMORY_VERSION` / `III_VERSION` build args in the + service's *Variables* tab to lock a specific release. diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile index e80119e7..62d3c39d 100644 --- a/deploy/render/Dockerfile +++ b/deploy/render/Dockerfile @@ -1,13 +1,34 @@ -FROM rohitghumare64/agentmemory:latest +FROM node:22-slim + +ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data -COPY --chmod=0755 --chown=65532:65532 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && rm -rf /var/lib/apt/lists/* + +RUN ARCH=$(uname -m) \ + && case "$ARCH" in \ + x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ + aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ + *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ + esac \ + && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ + | tar -xz -C /usr/local/bin/ \ + && chmod +x /usr/local/bin/iii + +RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit + +RUN mkdir -p /data && chown -R node:node /data + +COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh -USER 65532:65532 +USER node:node EXPOSE 3111 -ENTRYPOINT ["/usr/local/bin/agentmemory-entrypoint.sh"] +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/render/README.md b/deploy/render/README.md index 9dc1e629..dad15f93 100644 --- a/deploy/render/README.md +++ b/deploy/render/README.md @@ -34,8 +34,9 @@ in the service settings. Future deploys are a single curl call: curl "https://api.render.com/deploy/srv-XXYYZZ?key=AABBCC" ``` -To roll out a specific image tag rather than rebuilding from the -Dockerfile, append `&imgURL=docker.io%2Frohitghumare64%2Fagentmemory%3A`. +To pin a specific `@agentmemory/agentmemory` release, set the +`AGENTMEMORY_VERSION` build arg in the service's *Environment* tab +before the next deploy. Same for `III_VERSION`. ## Capture the HMAC secret @@ -102,5 +103,5 @@ See for the current rate card. - Render restarts the service on every deploy. The HMAC secret survives because it lives on the disk, but expect a 10–30 s gap of 502s during rollouts. -- The published image `rohitghumare64/agentmemory:latest` must include - amd64 (Render does not run arm64 web services as of writing). +- Render runs amd64 only for web services. The Dockerfile selects the + matching iii binary automatically via `uname -m`. From 6a18e7ca1c534db04ba9980b982236fe22e41584 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 13 May 2026 23:01:39 +0100 Subject: [PATCH 04/10] feat(deploy): add Coolify self-hosted template Adds deploy/coolify/ alongside the existing fly / Railway / Render templates. Coolify (https://coolify.io/self-hosted) is the self-hosted-on-your-own-VPS option for operators who don't want their memories on a third-party managed plane. Same self-contained Dockerfile pattern as the other three (node:22-slim + iii binary + npm package, USER node, build-args for version pinning) plus a docker-compose.yml that Coolify's "Docker Compose" build pack consumes directly. Compose layer adds a HEALTHCHECK, a named volume for /data, and json-file log rotation matching the rest of the deploy surface. README walks the operator through: - Coolify dashboard flow (New Application -> Public Repository -> Docker Compose build pack -> Base Directory: deploy/coolify). - Capturing the first-boot HMAC secret from the Logs tab. - Viewer access via SSH tunnel from the Coolify host (port 3113 stays internal by default) and the optional second-domain + basic-auth pattern for sharing it. - HMAC rotation, /data backup paths (Restic / Borg / rsync / Coolify Backups), and VPS cost-floor pointers (Hetzner CX22, DO Basic Droplet, Vultr). Top-level deploy/README.md and README.md updated to list the new template and explain when to pick it (already running a VPS, want a self-hosted control plane, don't want a third-party host holding your memories). --- README.md | 3 + deploy/README.md | 8 +- deploy/coolify/Dockerfile | 37 +++++++++ deploy/coolify/README.md | 127 ++++++++++++++++++++++++++++++ deploy/coolify/docker-compose.yml | 31 ++++++++ deploy/coolify/entrypoint.sh | 37 +++++++++ 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 deploy/coolify/Dockerfile create mode 100644 deploy/coolify/README.md create mode 100644 deploy/coolify/docker-compose.yml create mode 100755 deploy/coolify/entrypoint.sh diff --git a/README.md b/README.md index d28b0add..193f8090 100644 --- a/README.md +++ b/README.md @@ -555,6 +555,9 @@ cost floors) live in [`deploy/`](./deploy/README.md): volume in the dashboard. - [`deploy/render`](./deploy/render/README.md) — Blueprint flow, automatic disk snapshots on paid plans. +- [`deploy/coolify`](./deploy/coolify/README.md) — self-hosted on your + own VPS via [Coolify](https://coolify.io/self-hosted); same Docker + Compose stack, you own the host and the data. Only port `3111` is published. The viewer on `3113` stays bound to loopback inside the container — every template's README documents the diff --git a/deploy/README.md b/deploy/README.md index 52b7588b..a3738b88 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -10,9 +10,10 @@ plaintext-bearer guard fires loud on any non-loopback misconfiguration. | Platform | Pitch | Cost floor | |----------|-------|------------| -| [fly.io](./fly/README.md) | Single machine with auto-stop. Cheapest idle cost; cold-start on first request after sleep. | ~$0.15/month at full idle | -| [Railway](./railway/README.md) | Push from GitHub, volume in the dashboard. Easiest dashboard flow. | $5/month (Hobby plan flat fee) | +| [fly.io](./fly/README.md) | Single machine with auto-stop. Cheapest idle cost on a managed host; cold-start on first request after sleep. | ~$0.15/month at full idle | +| [Railway](./railway/README.md) | Push from GitHub, volume in the dashboard. Easiest managed dashboard flow. | $5/month (Hobby plan flat fee) | | [Render](./render/README.md) | Blueprint-driven; persistent disk attaches automatically. Most "set it and forget it." | $7.25/month (Starter web + 1 GB disk) | +| [Coolify](./coolify/README.md) | Self-hosted on your own VPS. Same Docker Compose stack, you own the host and the data. | VPS cost only (Hetzner CX22 ~€3.79/month) | ## What every template guarantees @@ -39,6 +40,9 @@ plaintext-bearer guard fires loud on any non-loopback misconfiguration. monthly bill. - Pick **Render** if you want the most "set it and forget it" Blueprint flow with automatic disk snapshots on paid plans. +- Pick **Coolify** if you already run a VPS and want a self-hosted + control plane — same Docker Compose stack, no third-party host has + your memories. All three give you the same agentmemory API at the same port (3111) with the same auth model. Migrating between them later is a `tar` of diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile new file mode 100644 index 00000000..e1052699 --- /dev/null +++ b/deploy/coolify/Dockerfile @@ -0,0 +1,37 @@ +FROM node:22-slim + +ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 + +ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ + AGENTMEMORY_HMAC_FILE=/data/.hmac \ + AGENTMEMORY_DATA_DIR=/data + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && rm -rf /var/lib/apt/lists/* + +RUN ARCH=$(uname -m) \ + && case "$ARCH" in \ + x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ + aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ + *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ + esac \ + && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ + | tar -xz -C /usr/local/bin/ \ + && chmod +x /usr/local/bin/iii + +RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit + +RUN mkdir -p /data && chown -R node:node /data + +COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh + +USER node:node + +EXPOSE 3111 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD curl -fsS http://127.0.0.1:3111/agentmemory/livez || exit 1 + +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/coolify/README.md b/deploy/coolify/README.md new file mode 100644 index 00000000..f51c2f2e --- /dev/null +++ b/deploy/coolify/README.md @@ -0,0 +1,127 @@ +# Deploy agentmemory on Coolify + +[Coolify](https://coolify.io/self-hosted) is an open-source, self-hosted +Heroku/Render alternative that you run on your own VPS. This template +deploys agentmemory as a Coolify *Application* backed by a Docker +Compose stack — Coolify handles TLS termination, persistent volume +provisioning, log aggregation, and the deploy webhook for you. + +## What you get + +- A public HTTPS endpoint serving the agentmemory REST API on port 3111 + (Coolify proxies TLS via Traefik or Caddy, your choice in the + instance settings) +- A persistent Docker volume backing `/data` for memories, BM25 index, + and stream backlog +- An HTTP health-check at `/agentmemory/livez` configured directly in + the Dockerfile (`HEALTHCHECK` directive) — Coolify reuses it for + rolling-deploy decisions +- `AGENTMEMORY_REQUIRE_HTTPS=1` baked in so integration plugins refuse + to send the bearer token over plaintext HTTP to a non-loopback host + +## One-time setup + +1. **Open your Coolify dashboard** and click **+ New → Application**. +2. **Source**: pick *Public Repository*. Paste: + ``` + https://github.com/rohitg00/agentmemory + ``` + Branch: `main`. +3. **Build Pack**: select *Docker Compose*. +4. **Base Directory**: `deploy/coolify` +5. **Compose Path**: `docker-compose.yml` +6. Click **Save** then **Deploy**. + +That's it. Coolify clones the repo, builds the Dockerfile under +`deploy/coolify/`, provisions the `agentmemory-data` named volume on +the host, attaches Traefik (or Caddy) for the public domain, and starts +the service. + +## Capture the HMAC secret + +Once the deploy logs show the service is up, open the application's +**Logs** tab in Coolify and search for `AGENTMEMORY_SECRET=`. You will +see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. +Copy it into your client environment (`~/.bashrc`, Claude Desktop +config, etc.). The secret is never printed again on subsequent boots. + +## Verify the deployment + +```bash +curl "https:///agentmemory/livez" +# {"status":"ok"} +``` + +For an authenticated call, your client must send +`Authorization: Bearer `. + +## Viewer access (port 3113 stays internal) + +The viewer port is not exposed by the compose file on purpose — it +holds the unauthenticated admin surface in older releases and the +proxied surface in current ones, neither of which belongs on the open +internet. Two paths to reach it: + +**Option A — SSH tunnel from the Coolify host.** Coolify gives you SSH +access to the underlying VPS. From your laptop: + +```bash +ssh -L 3113:127.0.0.1:3113 @ +# inside the SSH session, find the container: +docker ps --filter name=agentmemory --format "{{.Names}}" +# tunnel into the container's port from the host: +docker exec -it sh -c "curl http://localhost:3113" +``` + +Cleaner version: bind the container's 3113 to the host's loopback by +adding `- "127.0.0.1:3113:3113"` to the `ports:` block in +`docker-compose.yml`, redeploy, then `ssh -L 3113:127.0.0.1:3113 +@` is enough. + +**Option B — expose 3113 as a second Coolify domain protected by HTTP +basic auth.** Coolify's per-service routing supports adding a second +public endpoint with basic-auth middleware. Useful if you want to +share the viewer with a teammate without giving them SSH. + +## Rotate the HMAC secret + +```bash +ssh @ +docker exec -it sh -c "rm /data/.hmac" +exit +``` + +Then click **Redeploy** in the Coolify dashboard. The next boot prints +a fresh secret to the logs. + +## Back up `/data` + +Coolify exposes the named volume on the host filesystem under +`/var/lib/docker/volumes/_agentmemory-data/_data`. Back it +up with your existing host-level snapshot tooling (Restic, Borg, +`rsync`, BTRFS snapshots, etc.) or via Coolify's built-in *Backups* +feature for Docker volumes. + +## Cost floor and resources + +- **Hardware**: the agentmemory container idles at ~150 MB RSS, climbs + to ~400 MB under steady traffic. The bundled iii engine adds another + ~80 MB. A 1 vCPU / 1 GB VPS is comfortably enough for a personal + install. +- **VPS providers commonly paired with Coolify**: Hetzner CX22 + (~€3.79/month), DigitalOcean Basic Droplet ($6/month), Vultr Cloud + Compute ($6/month). Coolify itself is free. +- **Volume storage**: tied to whatever block storage the VPS provides; + typically pennies per GB-month. + +## Known caveats + +- The Dockerfile builds on the Coolify host on every deploy. First + deploy takes ~2 minutes; cached layers shrink subsequent rebuilds to + under 30 seconds. Pin `AGENTMEMORY_VERSION` and `III_VERSION` in + `docker-compose.yml`'s `build.args` block to lock a specific release. +- Coolify's *Persistent Storage* tab will show `agentmemory-data` as a + managed volume — do not delete it from the dashboard if you want + your memories to survive a redeploy. +- arm64 hosts work — the iii binary selection in the Dockerfile uses + `uname -m` and downloads the matching tarball. diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml new file mode 100644 index 00000000..47ab6569 --- /dev/null +++ b/deploy/coolify/docker-compose.yml @@ -0,0 +1,31 @@ +services: + agentmemory: + build: + context: . + dockerfile: Dockerfile + args: + AGENTMEMORY_VERSION: "0.9.12" + III_VERSION: "0.11.2" + restart: unless-stopped + environment: + AGENTMEMORY_REQUIRE_HTTPS: "1" + AGENTMEMORY_HMAC_FILE: /data/.hmac + AGENTMEMORY_DATA_DIR: /data + ports: + - "3111:3111" + volumes: + - agentmemory-data:/data + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:3111/agentmemory/livez || exit 1"] + interval: 30s + timeout: 5s + start_period: 20s + retries: 3 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +volumes: + agentmemory-data: diff --git a/deploy/coolify/entrypoint.sh b/deploy/coolify/entrypoint.sh new file mode 100755 index 00000000..2f5159d5 --- /dev/null +++ b/deploy/coolify/entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# agentmemory first-boot entrypoint. +# +# On first boot, generates a 256-bit HMAC secret with openssl rand, +# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), +# and prints it to stdout exactly once so the operator can capture it +# from the Coolify deploy logs. On subsequent boots the file already +# exists and we just load it. +# +# To rotate: delete the file and restart the application from the +# Coolify dashboard. The next boot will print a fresh secret. + +set -eu + +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" + +mkdir -p "${DATA_DIR}" + +if [ ! -s "${HMAC_FILE}" ]; then + SECRET="$(openssl rand -hex 32)" + umask 077 + printf '%s\n' "${SECRET}" > "${HMAC_FILE}" + chmod 600 "${HMAC_FILE}" + echo "================================================================" + echo "agentmemory: generated HMAC secret on first boot" + echo "AGENTMEMORY_SECRET=${SECRET}" + echo "Copy this value now. It will not be printed again." + echo "Stored at: ${HMAC_FILE} (chmod 600)" + echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "================================================================" +fi + +AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +export AGENTMEMORY_SECRET + +exec agentmemory "$@" From f4c6e40f2021f098854d25a208908caeac61b166 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 14 May 2026 01:33:43 +0100 Subject: [PATCH 05/10] fix(deploy): COPY iii from iiidev/iii image, drop tarball download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the curl + tar + chmod sequence in all four Dockerfiles (fly / railway / render / coolify) with a single multi-stage COPY --from=iiidev/iii:${III_VERSION} /app/iii /usr/local/bin/iii. Why: - Drops the supply-chain risk CodeRabbit flagged on PR #361 (curl-and-extract without checksum verification). iiidev/iii is the same image agentmemory's docker-compose.yml already pulls — using Docker Hub's content-addressed manifest as the integrity boundary is the same trust we already extend on the local Compose path. - Drops the arch case statement (BuildKit picks the right manifest entry automatically from the multi-arch tag). - Drops 3 of the 4 apt-get packages from the bookkeeping side; curl stays for the eventual HEALTHCHECK probe. The iii binary lives at /app/iii inside the distroless iiidev/iii image (per github.com/iii-hq/iii engine/Dockerfile), and COPY into node:22-slim lands it at /usr/local/bin/iii with 755 permissions so any USER can exec it. ARG III_VERSION is now declared before the first FROM so it can be interpolated into the iii-image stage tag. ARG AGENTMEMORY_VERSION stays in the final stage where the npm install consumes it. --- deploy/coolify/Dockerfile | 17 ++++++----------- deploy/fly/Dockerfile | 17 ++++++----------- deploy/railway/Dockerfile | 17 ++++++----------- deploy/render/Dockerfile | 17 ++++++----------- 4 files changed, 24 insertions(+), 44 deletions(-) diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile index e1052699..d5e00926 100644 --- a/deploy/coolify/Dockerfile +++ b/deploy/coolify/Dockerfile @@ -1,25 +1,20 @@ +ARG III_VERSION=0.11.2 + +FROM iiidev/iii:${III_VERSION} AS iii-image + FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data RUN apt-get update \ - && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ && rm -rf /var/lib/apt/lists/* -RUN ARCH=$(uname -m) \ - && case "$ARCH" in \ - x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ - aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ - *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ - esac \ - && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ - | tar -xz -C /usr/local/bin/ \ - && chmod +x /usr/local/bin/iii +COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile index 62d3c39d..10101b8e 100644 --- a/deploy/fly/Dockerfile +++ b/deploy/fly/Dockerfile @@ -1,25 +1,20 @@ +ARG III_VERSION=0.11.2 + +FROM iiidev/iii:${III_VERSION} AS iii-image + FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data RUN apt-get update \ - && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ && rm -rf /var/lib/apt/lists/* -RUN ARCH=$(uname -m) \ - && case "$ARCH" in \ - x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ - aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ - *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ - esac \ - && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ - | tar -xz -C /usr/local/bin/ \ - && chmod +x /usr/local/bin/iii +COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile index 62d3c39d..10101b8e 100644 --- a/deploy/railway/Dockerfile +++ b/deploy/railway/Dockerfile @@ -1,25 +1,20 @@ +ARG III_VERSION=0.11.2 + +FROM iiidev/iii:${III_VERSION} AS iii-image + FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data RUN apt-get update \ - && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ && rm -rf /var/lib/apt/lists/* -RUN ARCH=$(uname -m) \ - && case "$ARCH" in \ - x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ - aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ - *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ - esac \ - && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ - | tar -xz -C /usr/local/bin/ \ - && chmod +x /usr/local/bin/iii +COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile index 62d3c39d..10101b8e 100644 --- a/deploy/render/Dockerfile +++ b/deploy/render/Dockerfile @@ -1,25 +1,20 @@ +ARG III_VERSION=0.11.2 + +FROM iiidev/iii:${III_VERSION} AS iii-image + FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ AGENTMEMORY_HMAC_FILE=/data/.hmac \ AGENTMEMORY_DATA_DIR=/data RUN apt-get update \ - && apt-get install -y --no-install-recommends curl openssl ca-certificates tini \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ && rm -rf /var/lib/apt/lists/* -RUN ARCH=$(uname -m) \ - && case "$ARCH" in \ - x86_64) III_TAR=iii-x86_64-unknown-linux-gnu.tar.gz ;; \ - aarch64) III_TAR=iii-aarch64-unknown-linux-gnu.tar.gz ;; \ - *) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \ - esac \ - && curl -fsSL "https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/${III_TAR}" \ - | tar -xz -C /usr/local/bin/ \ - && chmod +x /usr/local/bin/iii +COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit From f7b3c489c11840ee69d78ee8fbe77b4ef252da16 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 14 May 2026 09:54:39 +0100 Subject: [PATCH 06/10] fix(deploy): rewire iii config + /data perms + platform port binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit against fly.io / Railway / Render / Coolify docs and the agentmemory CLI source surfaced four bugs that would prevent every template from actually serving traffic on a managed host. This commit addresses all four together because they share the same fix surface (entrypoint + Dockerfile + platform config) and shipping them independently would leave intermediate states broken. 1. iii config bound 127.0.0.1 + used relative ./data paths. The npm-bundled @agentmemory/agentmemory/dist/iii-config.yaml hard- codes `host: 127.0.0.1` on iii-http and `file_path: ./data/...` on iii-state / iii-stream. agentmemory CLI's findIiiConfig() returns the first existing candidate from a 3-item list with the npm bundle at position 0, so a sibling iii-config.yaml in cwd does not override. On any managed platform the result is the REST API binding loopback (router can't reach) and state writing to a directory that's not the persistent mount. Fix: the entrypoint overwrites the bundled file at boot with a container-tuned config (host 0.0.0.0, /data/... absolute paths, no iii-exec because the CLI orchestrates the worker over WS). This needs the entrypoint to run as root, hence the Dockerfile change below. 2. /data volume permissions root:root vs USER node. Every managed platform mounts persistent volumes root-owned 0755. The earlier Dockerfile ran `RUN mkdir -p /data && chown -R node` at build time, but the volume overlay at runtime replaces /data with the platform's empty root-owned mountpoint, leaving the `node` USER unable to write state_store.db / .hmac. Fix: stay USER root through the image, add `gosu` to apt, do the chown in entrypoint.sh against the *runtime* /data, then drop to the unprivileged `node` user via `exec gosu node:node agentmemory` before exec'ing the CLI. Symmetric to the iii-init busybox sidecar pattern in the repo-root docker-compose.yml. 3. Render's PORT injection broke the published port. Render auto-sets PORT (default 10000) and routes the public proxy to that port; the container then must bind 0.0.0.0:$PORT. Our container always binds 3111 (Dockerfile EXPOSE + iii-http config), so Render's proxy was forwarding to 10000 and getting connection refused. Fix: render.yaml now sets envVars PORT=3111 to override Render's default, plus exposes AGENTMEMORY_VERSION / III_VERSION as envVars (Render translates envVars into docker build args automatically). Also drops the AGENTMEMORY_REQUIRE_HTTPS / AGENTMEMORY_HMAC_FILE / AGENTMEMORY_DATA_DIR entries — server-side code never reads any of them; entrypoint reads HMAC_FILE / DATA_DIR via ${VAR:-default} so defaults already apply. 4. Coolify compose `ports:` bypassed Traefik. Per Coolify docs, `ports: ["3111:3111"]` binds the port directly on the host outside of the proxy network. The deploy would have been reachable on `http://:3111` with no TLS termination and no domain routing. Fix: replaced `ports:` with `expose:` so the port is only reachable on the internal proxy network. Operator now sets the service domain in the Coolify UI as `https://:3111` (the `:3111` is Coolify's proxy hint, not a public port). Also added `SERVICE_FQDN_AGENTMEMORY_3111` to the environment so the container can know its public URL via Coolify's magic-env convention. Other minor fixes: - railway.json adds `requiredMountPath: /data` so Railway fails fast if the operator forgets to attach a volume. - fly.toml drops the dead-letter [env] block (server code does not read AGENTMEMORY_REQUIRE_HTTPS / AGENTMEMORY_HMAC_FILE / AGENTMEMORY_DATA_DIR; entrypoint defaults handle the latter two). - All 4 entrypoints unified — same script, same heredoc, copy with `cp -p` at build time. - READMEs updated to reflect the new bound port story, the manual Render Blueprint flow (Render's deploy-button auto-detection only scans repo-root render.yaml), the Coolify proxy / domain pattern, and the gosu privilege drop. Best-practice audit results (fly.toml, railway.json, render.yaml, docker-compose.yml) verified against current platform docs — fly.toml schema is clean, railway.json gets the requiredMountPath addition, render.yaml + coolify compose get the rewires above. --- README.md | 14 +++-- deploy/README.md | 24 +++++--- deploy/coolify/Dockerfile | 12 +--- deploy/coolify/README.md | 27 +++++---- deploy/coolify/docker-compose.yml | 8 +-- deploy/coolify/entrypoint.sh | 95 +++++++++++++++++++++++++------ deploy/fly/Dockerfile | 12 +--- deploy/fly/README.md | 6 +- deploy/fly/entrypoint.sh | 95 +++++++++++++++++++++++++------ deploy/fly/fly.toml | 5 -- deploy/railway/Dockerfile | 12 +--- deploy/railway/README.md | 9 ++- deploy/railway/entrypoint.sh | 95 +++++++++++++++++++++++++------ deploy/railway/railway.json | 3 +- deploy/render/Dockerfile | 12 +--- deploy/render/README.md | 12 +++- deploy/render/entrypoint.sh | 95 +++++++++++++++++++++++++------ deploy/render/render.yaml | 12 ++-- 18 files changed, 388 insertions(+), 160 deletions(-) diff --git a/README.md b/README.md index 193f8090..0aa3d5f2 100644 --- a/README.md +++ b/README.md @@ -532,12 +532,14 @@ npx -y @agentmemory/mcp

Deploy

One-click templates for managed hosts. Each one ships a self-contained -Dockerfile that pulls `@agentmemory/agentmemory` from npm and bundles -the `iii-engine` binary at build time — no pre-published image -required. Persistent storage mounts at `/data`, an HMAC secret is -generated on first boot, and `AGENTMEMORY_REQUIRE_HTTPS=1` is baked in -so bearer-auth integrations refuse to send tokens over plaintext HTTP -to a non-loopback host. +Dockerfile that pulls `@agentmemory/agentmemory` from npm and copies +the iii engine binary in from the official `iiidev/iii` Docker Hub +image — no pre-built agentmemory image required. Persistent storage +mounts at `/data`; the first-boot entrypoint overwrites the +npm-bundled iii config (which binds `127.0.0.1`) with a deploy-tuned +one that binds `0.0.0.0` and uses absolute `/data` paths, generates +the HMAC secret, then drops privileges from `root` to `node` via +`gosu` before exec'ing the agentmemory CLI.

Deploy to fly.io diff --git a/deploy/README.md b/deploy/README.md index a3738b88..56ce2101 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -2,11 +2,14 @@ Stand up agentmemory on managed infrastructure without rolling your own Docker host. Each template ships a self-contained Dockerfile that pulls -`@agentmemory/agentmemory` from npm at build time and bundles the -`iii-engine` binary alongside it — no pre-published image required. -Storage mounts at `/data`, an HMAC secret is generated on first boot, -and `AGENTMEMORY_REQUIRE_HTTPS=1` is baked in so the v0.9.12 -plaintext-bearer guard fires loud on any non-loopback misconfiguration. +`@agentmemory/agentmemory` from npm at build time and copies the iii +engine binary in from the official `iiidev/iii` image — no pre-built +agentmemory image required. Storage mounts at `/data`; an HMAC secret +is generated by the first-boot entrypoint and persisted to the volume. +The entrypoint overwrites the npm-bundled iii config with a +deploy-tuned one that binds `0.0.0.0` and uses absolute `/data` paths, +then drops privileges from `root` to `node` via `gosu` before +exec'ing the agentmemory CLI. | Platform | Pitch | Cost floor | |----------|-------|------------| @@ -27,10 +30,13 @@ plaintext-bearer guard fires loud on any non-loopback misconfiguration. - **Only port 3111 is exposed publicly.** The viewer on port 3113 stays bound to the container's localhost. Reach it via SSH tunnel (see each platform's README). -- **`AGENTMEMORY_REQUIRE_HTTPS=1`** baked in. Integration plugins will - refuse to send a bearer token over plaintext HTTP to a non-loopback - host — if a TLS termination upstream gets misconfigured, the client - fails loud instead of silently leaking the secret. +- **TLS upstream of the container.** Every managed platform terminates + TLS at its edge proxy; the templates publish a single internal port + (`3111`) to that proxy, never to the host. Integration plugins + configured with `AGENTMEMORY_REQUIRE_HTTPS=1` will refuse to send the + bearer over plaintext HTTP to a non-loopback host, so a + misconfigured TLS layer fails loud instead of silently leaking the + secret. ## Pick a platform diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile index d5e00926..019f953d 100644 --- a/deploy/coolify/Dockerfile +++ b/deploy/coolify/Dockerfile @@ -6,23 +6,15 @@ FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ - AGENTMEMORY_HMAC_FILE=/data/.hmac \ - AGENTMEMORY_DATA_DIR=/data - RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ && rm -rf /var/lib/apt/lists/* COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit -RUN mkdir -p /data && chown -R node:node /data - -COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -USER node:node +COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh EXPOSE 3111 diff --git a/deploy/coolify/README.md b/deploy/coolify/README.md index f51c2f2e..feec4757 100644 --- a/deploy/coolify/README.md +++ b/deploy/coolify/README.md @@ -8,16 +8,16 @@ provisioning, log aggregation, and the deploy webhook for you. ## What you get -- A public HTTPS endpoint serving the agentmemory REST API on port 3111 - (Coolify proxies TLS via Traefik or Caddy, your choice in the - instance settings) +- A public HTTPS endpoint serving the agentmemory REST API behind + Coolify's built-in Traefik/Caddy proxy. The container port (`3111`) + is exposed to the proxy network only — never bound to the host — so + TLS termination and domain routing stay under proxy control. - A persistent Docker volume backing `/data` for memories, BM25 index, - and stream backlog -- An HTTP health-check at `/agentmemory/livez` configured directly in - the Dockerfile (`HEALTHCHECK` directive) — Coolify reuses it for - rolling-deploy decisions -- `AGENTMEMORY_REQUIRE_HTTPS=1` baked in so integration plugins refuse - to send the bearer token over plaintext HTTP to a non-loopback host + and stream backlog. Coolify auto-prefixes the volume name with the + application's UUID so the data survives redeploys. +- An HTTP health-check at `/agentmemory/livez` declared in the + Dockerfile (`HEALTHCHECK` directive). Coolify reuses it for + rolling-deploy decisions. ## One-time setup @@ -30,12 +30,17 @@ provisioning, log aggregation, and the deploy webhook for you. 3. **Build Pack**: select *Docker Compose*. 4. **Base Directory**: `deploy/coolify` 5. **Compose Path**: `docker-compose.yml` -6. Click **Save** then **Deploy**. +6. Click **Save**, then on the application settings screen set a + **Domain** in the form `https://:3111` (the `:3111` + suffix tells Coolify's proxy which container port to forward to; + it still serves over 443/80 publicly). +7. Click **Deploy**. That's it. Coolify clones the repo, builds the Dockerfile under `deploy/coolify/`, provisions the `agentmemory-data` named volume on the host, attaches Traefik (or Caddy) for the public domain, and starts -the service. +the service. The container is reachable only through the proxy — there +is no published host port. ## Capture the HMAC secret diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml index 47ab6569..94372272 100644 --- a/deploy/coolify/docker-compose.yml +++ b/deploy/coolify/docker-compose.yml @@ -8,11 +8,9 @@ services: III_VERSION: "0.11.2" restart: unless-stopped environment: - AGENTMEMORY_REQUIRE_HTTPS: "1" - AGENTMEMORY_HMAC_FILE: /data/.hmac - AGENTMEMORY_DATA_DIR: /data - ports: - - "3111:3111" + - SERVICE_FQDN_AGENTMEMORY_3111 + expose: + - "3111" volumes: - agentmemory-data:/data healthcheck: diff --git a/deploy/coolify/entrypoint.sh b/deploy/coolify/entrypoint.sh index 2f5159d5..00d83811 100755 --- a/deploy/coolify/entrypoint.sh +++ b/deploy/coolify/entrypoint.sh @@ -1,37 +1,98 @@ #!/bin/sh # agentmemory first-boot entrypoint. # -# On first boot, generates a 256-bit HMAC secret with openssl rand, -# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), -# and prints it to stdout exactly once so the operator can capture it -# from the Coolify deploy logs. On subsequent boots the file already -# exists and we just load it. +# Runs as root so it can: +# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 +# and uses relative ./data paths) with a deploy-tuned version that +# binds 0.0.0.0 and uses absolute /data paths. +# 2. chown the platform-mounted /data volume to the runtime user +# (managed platforms mount volumes root-owned 755 by default). +# 3. Generate the HMAC secret on first boot and persist it to +# /data/.hmac (chmod 600) so the secret survives restarts. # -# To rotate: delete the file and restart the application from the -# Coolify dashboard. The next boot will print a fresh secret. +# Then it execs the agentmemory CLI under gosu as the unprivileged +# `node` user. set -eu -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +RUN_AS="node:node" +III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" + +mkdir -p "$DATA_DIR" +chown -R "$RUN_AS" "$DATA_DIR" -mkdir -p "${DATA_DIR}" +cat > "$III_CONFIG" <<'EOF' +workers: + - name: iii-http + config: + port: 3111 + host: 0.0.0.0 + default_timeout: 180000 + cors: + allowed_origins: + - "http://localhost:3111" + - "http://localhost:3113" + - "http://127.0.0.1:3111" + - "http://127.0.0.1:3113" + allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] + - name: iii-state + config: + adapter: + name: kv + config: + store_method: file_based + file_path: /data/state_store.db + - name: iii-queue + config: + adapter: + name: builtin + - name: iii-pubsub + config: + adapter: + name: local + - name: iii-cron + config: + adapter: + name: kv + - name: iii-stream + config: + port: 3112 + host: 0.0.0.0 + adapter: + name: kv + config: + store_method: file_based + file_path: /data/stream_store + - name: iii-observability + config: + enabled: true + service_name: agentmemory + exporter: memory + sampling_ratio: 1.0 + metrics_enabled: true + logs_enabled: true + logs_console_output: true +EOF +chown "$RUN_AS" "$III_CONFIG" -if [ ! -s "${HMAC_FILE}" ]; then +if [ ! -s "$HMAC_FILE" ]; then SECRET="$(openssl rand -hex 32)" umask 077 - printf '%s\n' "${SECRET}" > "${HMAC_FILE}" - chmod 600 "${HMAC_FILE}" + printf '%s\n' "$SECRET" > "$HMAC_FILE" + chmod 600 "$HMAC_FILE" + chown "$RUN_AS" "$HMAC_FILE" echo "================================================================" echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=${SECRET}" + echo "AGENTMEMORY_SECRET=$SECRET" echo "Copy this value now. It will not be printed again." - echo "Stored at: ${HMAC_FILE} (chmod 600)" - echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "Stored at: $HMAC_FILE (chmod 600)" + echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." echo "================================================================" fi -AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" export AGENTMEMORY_SECRET -exec agentmemory "$@" +exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile index 10101b8e..048db1bb 100644 --- a/deploy/fly/Dockerfile +++ b/deploy/fly/Dockerfile @@ -6,23 +6,15 @@ FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ - AGENTMEMORY_HMAC_FILE=/data/.hmac \ - AGENTMEMORY_DATA_DIR=/data - RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ && rm -rf /var/lib/apt/lists/* COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit -RUN mkdir -p /data && chown -R node:node /data - -COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -USER node:node +COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh EXPOSE 3111 diff --git a/deploy/fly/README.md b/deploy/fly/README.md index d78ee6b3..b715ac24 100644 --- a/deploy/fly/README.md +++ b/deploy/fly/README.md @@ -12,9 +12,9 @@ logs exactly once. - `auto_stop_machines = "stop"` and `min_machines_running = 0` — the machine sleeps when idle, so cost floor approaches $0 for low traffic - HTTP healthcheck at `/agentmemory/livez` every 30 s -- `AGENTMEMORY_REQUIRE_HTTPS=1` by default — clients that try to send - the bearer token over plaintext HTTP will refuse, matching the - v0.9.12 guard +- The HMAC bearer secret is generated on first boot inside the + container and persisted to `/data/.hmac` (chmod 600); the operator + copies it from the deploy logs once. ## One-time setup diff --git a/deploy/fly/entrypoint.sh b/deploy/fly/entrypoint.sh index 2249abce..00d83811 100755 --- a/deploy/fly/entrypoint.sh +++ b/deploy/fly/entrypoint.sh @@ -1,37 +1,98 @@ #!/bin/sh # agentmemory first-boot entrypoint. # -# On first boot, generates a 256-bit HMAC secret with openssl rand, -# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), -# and prints it to stdout exactly once so the operator can capture it -# from the platform's deploy logs. On subsequent boots the file already -# exists and we just load it. +# Runs as root so it can: +# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 +# and uses relative ./data paths) with a deploy-tuned version that +# binds 0.0.0.0 and uses absolute /data paths. +# 2. chown the platform-mounted /data volume to the runtime user +# (managed platforms mount volumes root-owned 755 by default). +# 3. Generate the HMAC secret on first boot and persist it to +# /data/.hmac (chmod 600) so the secret survives restarts. # -# To rotate: delete the file and restart the machine. The next boot -# will print a fresh secret. +# Then it execs the agentmemory CLI under gosu as the unprivileged +# `node` user. set -eu -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +RUN_AS="node:node" +III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" + +mkdir -p "$DATA_DIR" +chown -R "$RUN_AS" "$DATA_DIR" -mkdir -p "${DATA_DIR}" +cat > "$III_CONFIG" <<'EOF' +workers: + - name: iii-http + config: + port: 3111 + host: 0.0.0.0 + default_timeout: 180000 + cors: + allowed_origins: + - "http://localhost:3111" + - "http://localhost:3113" + - "http://127.0.0.1:3111" + - "http://127.0.0.1:3113" + allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] + - name: iii-state + config: + adapter: + name: kv + config: + store_method: file_based + file_path: /data/state_store.db + - name: iii-queue + config: + adapter: + name: builtin + - name: iii-pubsub + config: + adapter: + name: local + - name: iii-cron + config: + adapter: + name: kv + - name: iii-stream + config: + port: 3112 + host: 0.0.0.0 + adapter: + name: kv + config: + store_method: file_based + file_path: /data/stream_store + - name: iii-observability + config: + enabled: true + service_name: agentmemory + exporter: memory + sampling_ratio: 1.0 + metrics_enabled: true + logs_enabled: true + logs_console_output: true +EOF +chown "$RUN_AS" "$III_CONFIG" -if [ ! -s "${HMAC_FILE}" ]; then +if [ ! -s "$HMAC_FILE" ]; then SECRET="$(openssl rand -hex 32)" umask 077 - printf '%s\n' "${SECRET}" > "${HMAC_FILE}" - chmod 600 "${HMAC_FILE}" + printf '%s\n' "$SECRET" > "$HMAC_FILE" + chmod 600 "$HMAC_FILE" + chown "$RUN_AS" "$HMAC_FILE" echo "================================================================" echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=${SECRET}" + echo "AGENTMEMORY_SECRET=$SECRET" echo "Copy this value now. It will not be printed again." - echo "Stored at: ${HMAC_FILE} (chmod 600)" - echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "Stored at: $HMAC_FILE (chmod 600)" + echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." echo "================================================================" fi -AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" export AGENTMEMORY_SECRET -exec agentmemory "$@" +exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/fly/fly.toml b/deploy/fly/fly.toml index 775adf4e..62510a43 100644 --- a/deploy/fly/fly.toml +++ b/deploy/fly/fly.toml @@ -39,11 +39,6 @@ primary_region = "iad" method = "GET" path = "/agentmemory/livez" -[env] - AGENTMEMORY_REQUIRE_HTTPS = "1" - AGENTMEMORY_HMAC_FILE = "/data/.hmac" - AGENTMEMORY_DATA_DIR = "/data" - [[vm]] size = "shared-cpu-1x" memory = "512mb" diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile index 10101b8e..048db1bb 100644 --- a/deploy/railway/Dockerfile +++ b/deploy/railway/Dockerfile @@ -6,23 +6,15 @@ FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ - AGENTMEMORY_HMAC_FILE=/data/.hmac \ - AGENTMEMORY_DATA_DIR=/data - RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ && rm -rf /var/lib/apt/lists/* COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit -RUN mkdir -p /data && chown -R node:node /data - -COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -USER node:node +COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh EXPOSE 3111 diff --git a/deploy/railway/README.md b/deploy/railway/README.md index 0c5dd849..9aad4fb3 100644 --- a/deploy/railway/README.md +++ b/deploy/railway/README.md @@ -11,9 +11,12 @@ deploy logs and copy it into your client. - A persistent Railway Volume at `/data` for memories, BM25 index, and stream backlog - Railway healthcheck against `/agentmemory/livez` -- `AGENTMEMORY_REQUIRE_HTTPS=1` by default — clients that try to send - the bearer token over plaintext HTTP refuse, matching the v0.9.12 - guard +- The HMAC bearer secret is generated on first boot inside the + container and persisted to `/data/.hmac` (chmod 600); the operator + copies it from the deploy logs once. +- The deploy uses `requiredMountPath: /data` so Railway refuses to + start the service if no volume is attached at that path — first + deploy must create the volume from the dashboard. ## Deploy via Railway dashboard diff --git a/deploy/railway/entrypoint.sh b/deploy/railway/entrypoint.sh index faf66a82..00d83811 100755 --- a/deploy/railway/entrypoint.sh +++ b/deploy/railway/entrypoint.sh @@ -1,37 +1,98 @@ #!/bin/sh # agentmemory first-boot entrypoint. # -# On first boot, generates a 256-bit HMAC secret with openssl rand, -# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), -# and prints it to stdout exactly once so the operator can capture it -# from the platform's deploy logs. On subsequent boots the file already -# exists and we just load it. +# Runs as root so it can: +# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 +# and uses relative ./data paths) with a deploy-tuned version that +# binds 0.0.0.0 and uses absolute /data paths. +# 2. chown the platform-mounted /data volume to the runtime user +# (managed platforms mount volumes root-owned 755 by default). +# 3. Generate the HMAC secret on first boot and persist it to +# /data/.hmac (chmod 600) so the secret survives restarts. # -# To rotate: delete the file and restart the service. The next boot -# will print a fresh secret. +# Then it execs the agentmemory CLI under gosu as the unprivileged +# `node` user. set -eu -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +RUN_AS="node:node" +III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" + +mkdir -p "$DATA_DIR" +chown -R "$RUN_AS" "$DATA_DIR" -mkdir -p "${DATA_DIR}" +cat > "$III_CONFIG" <<'EOF' +workers: + - name: iii-http + config: + port: 3111 + host: 0.0.0.0 + default_timeout: 180000 + cors: + allowed_origins: + - "http://localhost:3111" + - "http://localhost:3113" + - "http://127.0.0.1:3111" + - "http://127.0.0.1:3113" + allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] + - name: iii-state + config: + adapter: + name: kv + config: + store_method: file_based + file_path: /data/state_store.db + - name: iii-queue + config: + adapter: + name: builtin + - name: iii-pubsub + config: + adapter: + name: local + - name: iii-cron + config: + adapter: + name: kv + - name: iii-stream + config: + port: 3112 + host: 0.0.0.0 + adapter: + name: kv + config: + store_method: file_based + file_path: /data/stream_store + - name: iii-observability + config: + enabled: true + service_name: agentmemory + exporter: memory + sampling_ratio: 1.0 + metrics_enabled: true + logs_enabled: true + logs_console_output: true +EOF +chown "$RUN_AS" "$III_CONFIG" -if [ ! -s "${HMAC_FILE}" ]; then +if [ ! -s "$HMAC_FILE" ]; then SECRET="$(openssl rand -hex 32)" umask 077 - printf '%s\n' "${SECRET}" > "${HMAC_FILE}" - chmod 600 "${HMAC_FILE}" + printf '%s\n' "$SECRET" > "$HMAC_FILE" + chmod 600 "$HMAC_FILE" + chown "$RUN_AS" "$HMAC_FILE" echo "================================================================" echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=${SECRET}" + echo "AGENTMEMORY_SECRET=$SECRET" echo "Copy this value now. It will not be printed again." - echo "Stored at: ${HMAC_FILE} (chmod 600)" - echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "Stored at: $HMAC_FILE (chmod 600)" + echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." echo "================================================================" fi -AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" export AGENTMEMORY_SECRET -exec agentmemory "$@" +exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/railway/railway.json b/deploy/railway/railway.json index 87270d52..43f52173 100644 --- a/deploy/railway/railway.json +++ b/deploy/railway/railway.json @@ -9,6 +9,7 @@ "healthcheckPath": "/agentmemory/livez", "healthcheckTimeout": 30, "restartPolicyType": "ON_FAILURE", - "restartPolicyMaxRetries": 10 + "restartPolicyMaxRetries": 10, + "requiredMountPath": "/data" } } diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile index 10101b8e..048db1bb 100644 --- a/deploy/render/Dockerfile +++ b/deploy/render/Dockerfile @@ -6,23 +6,15 @@ FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 -ENV AGENTMEMORY_REQUIRE_HTTPS=1 \ - AGENTMEMORY_HMAC_FILE=/data/.hmac \ - AGENTMEMORY_DATA_DIR=/data - RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini curl \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ && rm -rf /var/lib/apt/lists/* COPY --from=iii-image /app/iii /usr/local/bin/iii RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit -RUN mkdir -p /data && chown -R node:node /data - -COPY --chmod=0755 --chown=node:node entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -USER node:node +COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh EXPOSE 3111 diff --git a/deploy/render/README.md b/deploy/render/README.md index dad15f93..3b73f7ee 100644 --- a/deploy/render/README.md +++ b/deploy/render/README.md @@ -8,15 +8,21 @@ logs exactly once. ## What you get - A public HTTPS endpoint serving the agentmemory REST API on port 3111 + (Render injects `PORT` defaulting to 10000; we override it to 3111 + via `envVars` so the published port matches the container's bind) - A 1 GB persistent disk at `/data` for memories, BM25 index, and stream backlog - Render healthcheck against `/agentmemory/livez` -- `AGENTMEMORY_REQUIRE_HTTPS=1` by default — clients that try to send - the bearer token over plaintext HTTP refuse, matching the v0.9.12 - guard +- The HMAC bearer secret is generated on first boot inside the + container and persisted to `/data/.hmac` (chmod 600); the operator + copies it from the deploy logs once. ## Deploy via Render Blueprint +Render's one-click deploy button only auto-detects `render.yaml` at the +repository root, which the agentmemory repo keeps clean. Use the +dashboard's manual Blueprint flow instead: + 1. Push the `deploy/render/` directory to a Git provider Render can reach (a fork of `rohitg00/agentmemory` works). 2. In the Render dashboard, click **New +** → **Blueprint**. diff --git a/deploy/render/entrypoint.sh b/deploy/render/entrypoint.sh index faf66a82..00d83811 100755 --- a/deploy/render/entrypoint.sh +++ b/deploy/render/entrypoint.sh @@ -1,37 +1,98 @@ #!/bin/sh # agentmemory first-boot entrypoint. # -# On first boot, generates a 256-bit HMAC secret with openssl rand, -# writes it to ${AGENTMEMORY_HMAC_FILE} (default /data/.hmac, chmod 600), -# and prints it to stdout exactly once so the operator can capture it -# from the platform's deploy logs. On subsequent boots the file already -# exists and we just load it. +# Runs as root so it can: +# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 +# and uses relative ./data paths) with a deploy-tuned version that +# binds 0.0.0.0 and uses absolute /data paths. +# 2. chown the platform-mounted /data volume to the runtime user +# (managed platforms mount volumes root-owned 755 by default). +# 3. Generate the HMAC secret on first boot and persist it to +# /data/.hmac (chmod 600) so the secret survives restarts. # -# To rotate: delete the file and restart the service. The next boot -# will print a fresh secret. +# Then it execs the agentmemory CLI under gosu as the unprivileged +# `node` user. set -eu -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" +HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" +RUN_AS="node:node" +III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" + +mkdir -p "$DATA_DIR" +chown -R "$RUN_AS" "$DATA_DIR" -mkdir -p "${DATA_DIR}" +cat > "$III_CONFIG" <<'EOF' +workers: + - name: iii-http + config: + port: 3111 + host: 0.0.0.0 + default_timeout: 180000 + cors: + allowed_origins: + - "http://localhost:3111" + - "http://localhost:3113" + - "http://127.0.0.1:3111" + - "http://127.0.0.1:3113" + allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] + - name: iii-state + config: + adapter: + name: kv + config: + store_method: file_based + file_path: /data/state_store.db + - name: iii-queue + config: + adapter: + name: builtin + - name: iii-pubsub + config: + adapter: + name: local + - name: iii-cron + config: + adapter: + name: kv + - name: iii-stream + config: + port: 3112 + host: 0.0.0.0 + adapter: + name: kv + config: + store_method: file_based + file_path: /data/stream_store + - name: iii-observability + config: + enabled: true + service_name: agentmemory + exporter: memory + sampling_ratio: 1.0 + metrics_enabled: true + logs_enabled: true + logs_console_output: true +EOF +chown "$RUN_AS" "$III_CONFIG" -if [ ! -s "${HMAC_FILE}" ]; then +if [ ! -s "$HMAC_FILE" ]; then SECRET="$(openssl rand -hex 32)" umask 077 - printf '%s\n' "${SECRET}" > "${HMAC_FILE}" - chmod 600 "${HMAC_FILE}" + printf '%s\n' "$SECRET" > "$HMAC_FILE" + chmod 600 "$HMAC_FILE" + chown "$RUN_AS" "$HMAC_FILE" echo "================================================================" echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=${SECRET}" + echo "AGENTMEMORY_SECRET=$SECRET" echo "Copy this value now. It will not be printed again." - echo "Stored at: ${HMAC_FILE} (chmod 600)" - echo "To rotate: delete ${HMAC_FILE} on the persistent volume and restart." + echo "Stored at: $HMAC_FILE (chmod 600)" + echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." echo "================================================================" fi -AGENTMEMORY_SECRET="$(cat "${HMAC_FILE}")" +AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" export AGENTMEMORY_SECRET -exec agentmemory "$@" +exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/render/render.yaml b/deploy/render/render.yaml index 986f1a46..7bd89a63 100644 --- a/deploy/render/render.yaml +++ b/deploy/render/render.yaml @@ -12,9 +12,9 @@ services: mountPath: /data sizeGB: 1 envVars: - - key: AGENTMEMORY_REQUIRE_HTTPS - value: "1" - - key: AGENTMEMORY_HMAC_FILE - value: /data/.hmac - - key: AGENTMEMORY_DATA_DIR - value: /data + - key: PORT + value: "3111" + - key: AGENTMEMORY_VERSION + value: "0.9.12" + - key: III_VERSION + value: "0.11.2" From a8ab4edba99b71e0545f770e5ff1e72431f4da2f Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 14 May 2026 10:09:22 +0100 Subject: [PATCH 07/10] fix(deploy): pin iii-sdk to 0.11.2 via npm overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agentmemory@0.9.12 declares `iii-sdk: ^0.11.2`, which `npm install` caret-resolves to **0.11.6** as of writing. That bumps the worker SDK ahead of the engine the agentmemory repo pins (iii v0.11.2). The two versions are wire-compatible in the calls agentmemory uses today, but the policy intent in the repo is single-version lockstep until the v0.11.6 sandbox-everything model is wired through — running SDK 0.11.6 against engine 0.11.2 in production silently re-introduces the very drift the AGENTMEMORY_III_VERSION pin exists to prevent. `npm install -g` ignores the `overrides` field, so we cannot fix this with the existing global install. Switch each Dockerfile to a local install under /opt/agentmemory with a one-line package.json carrying: overrides: { iii-sdk: 0.11.2 } then symlink the produced `node_modules/.bin/agentmemory` into `/usr/local/bin/agentmemory` so the entrypoint invocation stays identical. Verified locally that this resolves `node_modules/iii-sdk/package.json` to version 0.11.2 (was 0.11.6 without the override). Side effects: - Entrypoint III_CONFIG path moves from `/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml` (global) to `/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml` (local). agentmemory CLI's findIiiConfig() resolves __dirname through the symlink to the local install so candidate-0 lookup still hits the file we overwrite. - New ARG `III_SDK_VERSION=0.11.2` declared in every Dockerfile, surfaced as a `build.args` field in coolify's compose and as an envVar in render.yaml so operators can pin a different SDK version if they manually migrate to engine 0.11.6+ down the line. - AGENTMEMORY_III_VERSION env now baked into the image (matches the engine pin) so the CLI's iii-binary download fallback path also resolves to the right version if PATH lookup ever misses. --- deploy/coolify/Dockerfile | 9 ++++++++- deploy/coolify/docker-compose.yml | 1 + deploy/coolify/entrypoint.sh | 2 +- deploy/fly/Dockerfile | 15 ++++++++++++++- deploy/fly/entrypoint.sh | 2 +- deploy/railway/Dockerfile | 15 ++++++++++++++- deploy/railway/entrypoint.sh | 2 +- deploy/render/Dockerfile | 15 ++++++++++++++- deploy/render/entrypoint.sh | 2 +- deploy/render/render.yaml | 2 ++ 10 files changed, 57 insertions(+), 8 deletions(-) diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile index 019f953d..a7cf2cf4 100644 --- a/deploy/coolify/Dockerfile +++ b/deploy/coolify/Dockerfile @@ -5,6 +5,8 @@ FROM iiidev/iii:${III_VERSION} AS iii-image FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 +ARG III_SDK_VERSION=0.11.2 RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ @@ -12,7 +14,12 @@ RUN apt-get update \ COPY --from=iii-image /app/iii /usr/local/bin/iii -RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit +WORKDIR /opt/agentmemory +RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ + && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ + && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory + +ENV AGENTMEMORY_III_VERSION=${III_VERSION} COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml index 94372272..28c136bf 100644 --- a/deploy/coolify/docker-compose.yml +++ b/deploy/coolify/docker-compose.yml @@ -6,6 +6,7 @@ services: args: AGENTMEMORY_VERSION: "0.9.12" III_VERSION: "0.11.2" + III_SDK_VERSION: "0.11.2" restart: unless-stopped environment: - SERVICE_FQDN_AGENTMEMORY_3111 diff --git a/deploy/coolify/entrypoint.sh b/deploy/coolify/entrypoint.sh index 00d83811..ffdd6333 100755 --- a/deploy/coolify/entrypoint.sh +++ b/deploy/coolify/entrypoint.sh @@ -18,7 +18,7 @@ set -eu DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" RUN_AS="node:node" -III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" +III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" mkdir -p "$DATA_DIR" chown -R "$RUN_AS" "$DATA_DIR" diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile index 048db1bb..ce6cb1ef 100644 --- a/deploy/fly/Dockerfile +++ b/deploy/fly/Dockerfile @@ -5,6 +5,8 @@ FROM iiidev/iii:${III_VERSION} AS iii-image FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 +ARG III_SDK_VERSION=0.11.2 RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ @@ -12,7 +14,18 @@ RUN apt-get update \ COPY --from=iii-image /app/iii /usr/local/bin/iii -RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit +# Install agentmemory into a dedicated prefix so the local package.json's +# `overrides` field pins iii-sdk down to match the engine (agentmemory's +# caret range `^0.11.2` otherwise resolves to 0.11.6, the version that +# requires the new sandbox-everything worker model the agentmemory CLI +# is not refactored for yet). `npm install -g` ignores overrides, hence +# the local prefix. +WORKDIR /opt/agentmemory +RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ + && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ + && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory + +ENV AGENTMEMORY_III_VERSION=${III_VERSION} COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh diff --git a/deploy/fly/entrypoint.sh b/deploy/fly/entrypoint.sh index 00d83811..ffdd6333 100755 --- a/deploy/fly/entrypoint.sh +++ b/deploy/fly/entrypoint.sh @@ -18,7 +18,7 @@ set -eu DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" RUN_AS="node:node" -III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" +III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" mkdir -p "$DATA_DIR" chown -R "$RUN_AS" "$DATA_DIR" diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile index 048db1bb..ce6cb1ef 100644 --- a/deploy/railway/Dockerfile +++ b/deploy/railway/Dockerfile @@ -5,6 +5,8 @@ FROM iiidev/iii:${III_VERSION} AS iii-image FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 +ARG III_SDK_VERSION=0.11.2 RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ @@ -12,7 +14,18 @@ RUN apt-get update \ COPY --from=iii-image /app/iii /usr/local/bin/iii -RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit +# Install agentmemory into a dedicated prefix so the local package.json's +# `overrides` field pins iii-sdk down to match the engine (agentmemory's +# caret range `^0.11.2` otherwise resolves to 0.11.6, the version that +# requires the new sandbox-everything worker model the agentmemory CLI +# is not refactored for yet). `npm install -g` ignores overrides, hence +# the local prefix. +WORKDIR /opt/agentmemory +RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ + && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ + && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory + +ENV AGENTMEMORY_III_VERSION=${III_VERSION} COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh diff --git a/deploy/railway/entrypoint.sh b/deploy/railway/entrypoint.sh index 00d83811..ffdd6333 100755 --- a/deploy/railway/entrypoint.sh +++ b/deploy/railway/entrypoint.sh @@ -18,7 +18,7 @@ set -eu DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" RUN_AS="node:node" -III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" +III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" mkdir -p "$DATA_DIR" chown -R "$RUN_AS" "$DATA_DIR" diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile index 048db1bb..ce6cb1ef 100644 --- a/deploy/render/Dockerfile +++ b/deploy/render/Dockerfile @@ -5,6 +5,8 @@ FROM iiidev/iii:${III_VERSION} AS iii-image FROM node:22-slim ARG AGENTMEMORY_VERSION=0.9.12 +ARG III_VERSION=0.11.2 +ARG III_SDK_VERSION=0.11.2 RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ @@ -12,7 +14,18 @@ RUN apt-get update \ COPY --from=iii-image /app/iii /usr/local/bin/iii -RUN npm install -g "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit +# Install agentmemory into a dedicated prefix so the local package.json's +# `overrides` field pins iii-sdk down to match the engine (agentmemory's +# caret range `^0.11.2` otherwise resolves to 0.11.6, the version that +# requires the new sandbox-everything worker model the agentmemory CLI +# is not refactored for yet). `npm install -g` ignores overrides, hence +# the local prefix. +WORKDIR /opt/agentmemory +RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ + && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ + && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory + +ENV AGENTMEMORY_III_VERSION=${III_VERSION} COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh diff --git a/deploy/render/entrypoint.sh b/deploy/render/entrypoint.sh index 00d83811..ffdd6333 100755 --- a/deploy/render/entrypoint.sh +++ b/deploy/render/entrypoint.sh @@ -18,7 +18,7 @@ set -eu DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" RUN_AS="node:node" -III_CONFIG="/usr/local/lib/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" +III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" mkdir -p "$DATA_DIR" chown -R "$RUN_AS" "$DATA_DIR" diff --git a/deploy/render/render.yaml b/deploy/render/render.yaml index 7bd89a63..c96516d0 100644 --- a/deploy/render/render.yaml +++ b/deploy/render/render.yaml @@ -18,3 +18,5 @@ services: value: "0.9.12" - key: III_VERSION value: "0.11.2" + - key: III_SDK_VERSION + value: "0.11.2" From 80cf87cd28647f1b7f232604bbfda70a1c68e226 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 14 May 2026 10:41:53 +0100 Subject: [PATCH 08/10] fix(deploy): set TINI_SUBREAPER=1 so tini reaps zombies on Fly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end deploy to fly.io surfaced the warning: [WARN tini (645)] Tini is not running as PID 1 and isn't registered as a child subreaper. Zombie processes will not be re-parented to Tini, so zombie reaping won't work. Fly's init system holds PID 1, so tini lands as a child process and the agentmemory CLI (which spawns iii detached) ends up with no process reaping its eventual zombies. Setting TINI_SUBREAPER=1 triggers prctl(PR_SET_CHILD_SUBREAPER, 1) so tini still reaps descendant zombies even when it isn't PID 1. Same fix applies to Railway and Render (which also wrap our entrypoint under their own init) and to Coolify when the operator runs the container under any PID-1-grabbing supervisor like systemd-nspawn. Verified end-to-end against fly.io: - App agentmemory-ghumare64 deployed via remote builder - 114 MB image (iii engine + node:22-slim + npm packages) - 1 GB volume in iad provisioned and mounted - First-boot HMAC generated and persisted to /data/.hmac - iii-engine + agentmemory worker came up clean - Worker registered with iii engine over ws://localhost:49134 - /agentmemory/livez returned 200 in 220 ms - Bearer auth gating verified: 401 without secret, 200/201 with it - POST /agentmemory/observe ingested a synthetic observation - POST /agentmemory/smart-search returned a valid response envelope Only outstanding log noise after this commit is a Node punycode deprecation warning from a transitive dep — not actionable from this side. --- deploy/coolify/Dockerfile | 3 ++- deploy/fly/Dockerfile | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile index a7cf2cf4..3563bfd8 100644 --- a/deploy/coolify/Dockerfile +++ b/deploy/coolify/Dockerfile @@ -19,7 +19,8 @@ RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overr && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory -ENV AGENTMEMORY_III_VERSION=${III_VERSION} +ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ + TINI_SUBREAPER=1 COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile index ce6cb1ef..89257f93 100644 --- a/deploy/fly/Dockerfile +++ b/deploy/fly/Dockerfile @@ -25,7 +25,8 @@ RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overr && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory -ENV AGENTMEMORY_III_VERSION=${III_VERSION} +ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ + TINI_SUBREAPER=1 COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh From d77dab3c5ef2150368c17e9be0910ae4e868cd66 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 14 May 2026 10:49:46 +0100 Subject: [PATCH 09/10] fix(deploy): backfill TINI_SUBREAPER=1 on railway + render Dockerfiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit missed these two — same reasoning applies (Railway and Render both wrap our entrypoint under their own platform init, so tini lands as a non-PID-1 child and zombie reaping needs the prctl subreaper bit set). --- deploy/railway/Dockerfile | 3 ++- deploy/render/Dockerfile | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile index ce6cb1ef..89257f93 100644 --- a/deploy/railway/Dockerfile +++ b/deploy/railway/Dockerfile @@ -25,7 +25,8 @@ RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overr && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory -ENV AGENTMEMORY_III_VERSION=${III_VERSION} +ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ + TINI_SUBREAPER=1 COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile index ce6cb1ef..89257f93 100644 --- a/deploy/render/Dockerfile +++ b/deploy/render/Dockerfile @@ -25,7 +25,8 @@ RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overr && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory -ENV AGENTMEMORY_III_VERSION=${III_VERSION} +ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ + TINI_SUBREAPER=1 COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh From 298a8da1048f6f60835dac30d124a19920d054d8 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 14 May 2026 11:21:46 +0100 Subject: [PATCH 10/10] fix(deploy): apply live-fly-deploy findings across all templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real end-to-end deploy on fly.io surfaced four refinements that generalise across the other three templates. This commit ports them. 1. Health-check grace_period: 10 s -> 30 s. Measured cold start on a fresh fly.io machine in `iad`: machine image prepared : 5.1 s volume mount + format : 2.5 s firecracker boot : 1.0 s entrypoint + chown : 0.5 s iii-engine ready : 3.0 s agentmemory worker reg : 2.0 s -------------------------------- healthcheck passes : ~9-10 s `grace_period = "10s"` (fly.toml) and `start_period: 20s` (coolify compose + Dockerfile HEALTHCHECK) were both within the noise of the measured time. Bumped both to 30 s — gives a 3x safety margin without affecting normal-operation health-check cadence. Railway and Render manage their own initial-deploy grace windows server-side, so railway.json's `healthcheckTimeout: 30` (per-attempt) and render.yaml's `healthCheckPath` stay as-is. 2. Documented LLM + embedding provider env-var menu in deploy/README.md. Live deploy logs surface a clear warning: [agentmemory] No LLM provider key found (ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY). LLM-backed compression and summarization are DISABLED -- using no-op provider. The deploy READMEs never told operators how to opt in. Added a table covering ANTHROPIC / GEMINI / OPENROUTER for LLM, OPENAI / VOYAGE for embeddings, plus AGENTMEMORY_AUTO_COMPRESS and AGENTMEMORY_INJECT_CONTEXT for the corresponding behaviour flags. Each platform's variable-injection surface (flyctl secrets, dashboard Environment tab) is named in the same table. 3. Captured cold-start budget in deploy/README.md. Same step-by-step measured timing above is now in the shared README so operators have a concrete expectation rather than guessing why first-byte after `fly deploy` takes ten seconds. Notes that every template's start window is sized for 3x of this. 4. fly README: documented shared-IPv4 + dedicated-IPv6 default. Fly assigns one of each by default at no charge. Legacy clients without SNI that need a dedicated IPv4 can buy one for $2/month via `fly ips allocate-v4`. Added the build-time stats observed in the real deploy (~30 s first build, ~10 s cached rebuild, 114 MB image) to the Known Caveats section so operators sizing build-minute budgets have real numbers. No code paths changed -- only TOML / YAML / Markdown surface tweaks and one HEALTHCHECK `start_period` bump in the coolify Dockerfile. --- deploy/README.md | 46 ++++++++++++++++++++++++++++++- deploy/coolify/Dockerfile | 2 +- deploy/coolify/docker-compose.yml | 2 +- deploy/fly/README.md | 10 +++++-- deploy/fly/fly.toml | 2 +- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/deploy/README.md b/deploy/README.md index 56ce2101..91aa199e 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -50,7 +50,51 @@ exec'ing the agentmemory CLI. control plane — same Docker Compose stack, no third-party host has your memories. -All three give you the same agentmemory API at the same port (3111) +All four give you the same agentmemory API at the same port (3111) with the same auth model. Migrating between them later is a `tar` of `/data` and a re-import — see each platform's README for the exact commands. + +## Optional: LLM + embedding provider keys + +Every template runs out of the box without any LLM or embedding key — +search falls back to BM25-only mode and synthetic (zero-LLM) +compression keeps memories indexable. To unlock LLM-powered +compression and hybrid (BM25 + vector) recall, add one of the +following to your platform's environment variables (Fly: +`flyctl secrets set`; Railway / Render / Coolify: dashboard +*Variables / Environment* tab): + +| Variable | Purpose | +|---------------------------|----------------------------------------------------------| +| `ANTHROPIC_API_KEY` | LLM-backed compression + summarization | +| `GEMINI_API_KEY` | LLM provider alternative | +| `OPENROUTER_API_KEY` | LLM provider alternative | +| `OPENAI_API_KEY` | Embedding provider (text-embedding-3-small by default) | +| `VOYAGE_API_KEY` | Embedding provider alternative | +| `AGENTMEMORY_AUTO_COMPRESS=true` | Run LLM compression on every observation batch | +| `AGENTMEMORY_INJECT_CONTEXT=true` | Inject recalled memories back into agent prompts | + +The defaults are intentionally conservative: provider keys default to +absent (no third-party calls), `AGENTMEMORY_AUTO_COMPRESS` is off, +and `AGENTMEMORY_INJECT_CONTEXT` is off. Opt in only after you've +confirmed your provider quota can absorb the workload. + +## Cold-start budget + +Measured against fly.io's `iad` region with a 1 GB volume: + +``` +machine image prepared : 5.1 s +volume mount + format : 2.5 s +firecracker boot : 1.0 s +entrypoint + chown : 0.5 s +iii-engine ready : 3.0 s +agentmemory worker reg : 2.0 s +───────────────────────────────── +healthcheck passes : ~9-10 s +``` + +Every template's health-check `grace_period` (or compose +`start_period`) is set to 30 s for a 3x safety margin. Tune lower +once you've measured your own platform's image-pull characteristics. diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile index 3563bfd8..3f6c433f 100644 --- a/deploy/coolify/Dockerfile +++ b/deploy/coolify/Dockerfile @@ -26,7 +26,7 @@ COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh EXPOSE 3111 -HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD curl -fsS http://127.0.0.1:3111/agentmemory/livez || exit 1 ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml index 28c136bf..1bd48648 100644 --- a/deploy/coolify/docker-compose.yml +++ b/deploy/coolify/docker-compose.yml @@ -18,7 +18,7 @@ services: test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:3111/agentmemory/livez || exit 1"] interval: 30s timeout: 5s - start_period: 20s + start_period: 30s retries: 3 logging: driver: json-file diff --git a/deploy/fly/README.md b/deploy/fly/README.md index b715ac24..020d9751 100644 --- a/deploy/fly/README.md +++ b/deploy/fly/README.md @@ -119,8 +119,14 @@ See for the up-to-date rate card. second volume in another region and update `primary_region` after the failover, or take snapshots with `fly volumes snapshots create`. - The Dockerfile builds in the Fly Builder on every deploy — first - deploy takes ~2 minutes while npm + the iii binary download. Cached - layers shrink subsequent rebuilds to under 30 seconds. + build is ~30 seconds; cached layers shrink rebuilds to under 10 + seconds. Image is ~114 MB. +- First deploy lands on a **shared IPv4 + dedicated IPv6** by default + (free). If you need a dedicated IPv4 for legacy clients without SNI, + run `fly ips allocate-v4 --app "$APP"` — costs $2/month. +- Cold-start (from machine launch to passing `/agentmemory/livez`) is + ~9 seconds measured. `grace_period = "30s"` on the health check + gives a 3x safety margin. - Bump `AGENTMEMORY_VERSION` or `III_VERSION` in the Dockerfile to upgrade. `fly deploy --build-arg AGENTMEMORY_VERSION=` also works for a one-off without editing the file. diff --git a/deploy/fly/fly.toml b/deploy/fly/fly.toml index 62510a43..03f58bc9 100644 --- a/deploy/fly/fly.toml +++ b/deploy/fly/fly.toml @@ -35,7 +35,7 @@ primary_region = "iad" [[http_service.checks]] interval = "30s" timeout = "5s" - grace_period = "10s" + grace_period = "30s" method = "GET" path = "/agentmemory/livez"