chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# One-click deploy templates
|
||||
|
||||
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 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 |
|
||||
|----------|-------|------------|
|
||||
| [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
|
||||
|
||||
- **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.
|
||||
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).
|
||||
- **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
|
||||
|
||||
- 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.
|
||||
- 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 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.
|
||||
@@ -0,0 +1,32 @@
|
||||
ARG III_VERSION=0.11.2
|
||||
|
||||
FROM iiidev/iii:${III_VERSION} AS iii-image
|
||||
|
||||
FROM node:22-slim
|
||||
|
||||
ARG AGENTMEMORY_VERSION=0.9.27
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=iii-image /app/iii /usr/local/bin/iii
|
||||
|
||||
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} \
|
||||
TINI_SUBREAPER=1
|
||||
|
||||
COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh
|
||||
|
||||
EXPOSE 3111
|
||||
|
||||
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"]
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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 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. 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
|
||||
|
||||
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 on the application settings screen set a
|
||||
**Domain** in the form `https://<your-fqdn>: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 container is reachable only through the proxy — there
|
||||
is no published host port.
|
||||
|
||||
## 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://<your-coolify-domain>/agentmemory/livez"
|
||||
# {"status":"ok"}
|
||||
```
|
||||
|
||||
For an authenticated call, your client must send
|
||||
`Authorization: Bearer <secret>`.
|
||||
|
||||
## 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 <user>@<coolify-host>
|
||||
# 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 <container-name> 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
|
||||
<user>@<host>` 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 <user>@<coolify-host>
|
||||
docker exec -it <container-name> 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/<project-id>_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.
|
||||
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
agentmemory:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
AGENTMEMORY_VERSION: "0.9.27"
|
||||
III_VERSION: "0.11.2"
|
||||
III_SDK_VERSION: "0.11.2"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
SERVICE_FQDN_AGENTMEMORY_3111: ${SERVICE_FQDN_AGENTMEMORY_3111}
|
||||
expose:
|
||||
- "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: 30s
|
||||
retries: 3
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
agentmemory-data:
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/sh
|
||||
# agentmemory first-boot entrypoint.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Then it execs the agentmemory CLI under gosu as the unprivileged
|
||||
# `node` user.
|
||||
|
||||
set -eu
|
||||
|
||||
DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}"
|
||||
HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}"
|
||||
RUN_AS="node:node"
|
||||
III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml"
|
||||
|
||||
mkdir -p "$DATA_DIR"
|
||||
chown -R "$RUN_AS" "$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
|
||||
SECRET="$(openssl rand -hex 32)"
|
||||
umask 077
|
||||
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 "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 gosu "$RUN_AS" agentmemory "$@"
|
||||
@@ -0,0 +1,35 @@
|
||||
ARG III_VERSION=0.11.2
|
||||
|
||||
FROM iiidev/iii:${III_VERSION} AS iii-image
|
||||
|
||||
FROM node:22-slim
|
||||
|
||||
ARG AGENTMEMORY_VERSION=0.9.27
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=iii-image /app/iii /usr/local/bin/iii
|
||||
|
||||
# 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} \
|
||||
TINI_SUBREAPER=1
|
||||
|
||||
COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh
|
||||
|
||||
EXPOSE 3111
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"]
|
||||
@@ -0,0 +1,166 @@
|
||||
# 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
|
||||
- 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
|
||||
|
||||
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. 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"
|
||||
|
||||
# 4. Create the volume in the same region as the app:
|
||||
fly volumes create "$VOLUME" --region iad --size 1
|
||||
|
||||
# 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 "$APP" | 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,
|
||||
the viewer unlock prompt, etc.). The secret is never printed again on
|
||||
subsequent boots.
|
||||
|
||||
If the first-boot log line is no longer available, read the persisted
|
||||
secret from the mounted volume:
|
||||
|
||||
```bash
|
||||
fly ssh console --app "$APP" -C "sh -lc 'cat /data/.hmac'"
|
||||
```
|
||||
|
||||
## Verify the deployment
|
||||
|
||||
```bash
|
||||
curl "https://$APP.fly.dev/agentmemory/livez"
|
||||
# {"status":"ok"}
|
||||
```
|
||||
|
||||
For an authenticated call, your client must send `Authorization: Bearer <secret>`.
|
||||
|
||||
## Viewer access (port 3113 stays internal)
|
||||
|
||||
The viewer port is intentionally not exposed publicly. Tunnel to it:
|
||||
|
||||
```bash
|
||||
fly proxy 3113:3113 --app "$APP"
|
||||
# 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.
|
||||
|
||||
The entrypoint sets `AGENTMEMORY_VIEWER_HOST=::` **only when it detects
|
||||
Fly's runtime variables** (`FLY_APP_NAME` / `FLY_ALLOC_ID`). That makes
|
||||
the viewer listen on the machine's `fly-local-6pn` WireGuard interface
|
||||
as well as loopback so `fly proxy` can reach it. The same branch
|
||||
pre-seeds `VIEWER_ALLOWED_HOSTS=localhost:3113,127.0.0.1:3113,[::1]:3113`,
|
||||
which are the Host headers `fly proxy 3113:3113` actually emits on
|
||||
your laptop.
|
||||
|
||||
When `AGENTMEMORY_VIEWER_HOST` is non-loopback the viewer enforces two
|
||||
extra guards: it refuses to start unless `VIEWER_ALLOWED_HOSTS` is
|
||||
explicitly set, and every request to `/agentmemory/*` must present
|
||||
`Authorization: Bearer $AGENTMEMORY_SECRET`. Static HTML and the
|
||||
favicon are still served unauthenticated. If a proxied viewer request
|
||||
gets a 401, the browser UI prompts for `AGENTMEMORY_SECRET` and stores
|
||||
it in session storage so subsequent viewer API calls include the bearer.
|
||||
Use the value printed in the first-boot logs or read `/data/.hmac`
|
||||
inside the machine.
|
||||
|
||||
> **Security warning.** Setting `AGENTMEMORY_VIEWER_HOST=0.0.0.0` or
|
||||
> `::` turns the viewer into a network-reachable proxy that signs every
|
||||
> upstream call with `AGENTMEMORY_SECRET`. Never enable that outside a
|
||||
> network you trust (Fly's WireGuard mesh in this template), and never
|
||||
> set it in a plain `docker run -p 3113:3113 …` on a shared host — the
|
||||
> entrypoint deliberately skips the override when Fly env vars are
|
||||
> absent so a plain Docker pull stays loopback-only.
|
||||
|
||||
## Rotate the HMAC secret
|
||||
|
||||
```bash
|
||||
fly ssh console --app "$APP"
|
||||
rm /data/.hmac
|
||||
exit
|
||||
fly machine restart <machine-id>
|
||||
fly logs --app "$APP" | grep AGENTMEMORY_SECRET=
|
||||
```
|
||||
|
||||
Update every client with the new secret. Old tokens stop working
|
||||
immediately.
|
||||
|
||||
## Back up `/data`
|
||||
|
||||
```bash
|
||||
fly ssh console --app "$APP" -C "tar czf - /data" > "$APP-$(date +%Y%m%d).tar.gz"
|
||||
```
|
||||
|
||||
To restore on a fresh machine:
|
||||
|
||||
```bash
|
||||
cat "$APP-YYYYMMDD.tar.gz" | fly ssh console --app "$APP" -C "tar xzf - -C /"
|
||||
fly machine restart <machine-id>
|
||||
```
|
||||
|
||||
## 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 <https://fly.io/docs/about/pricing/> 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 Dockerfile builds in the Fly Builder on every deploy — first
|
||||
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=<x>` also works
|
||||
for a one-off without editing the file.
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
# agentmemory first-boot entrypoint.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Then it execs the agentmemory CLI under gosu as the unprivileged
|
||||
# `node` user.
|
||||
|
||||
set -eu
|
||||
|
||||
DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}"
|
||||
HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}"
|
||||
RUN_AS="node:node"
|
||||
III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml"
|
||||
|
||||
mkdir -p "$DATA_DIR"
|
||||
chown -R "$RUN_AS" "$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
|
||||
SECRET="$(openssl rand -hex 32)"
|
||||
umask 077
|
||||
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 "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
|
||||
|
||||
# The viewer's default 127.0.0.1 bind is unreachable through fly proxy,
|
||||
# which enters the machine via fly-local-6pn (IPv6). Opt into a
|
||||
# non-loopback bind ONLY when we're actually inside Fly (detected via
|
||||
# Fly's runtime variables). A plain `docker run` of this image will not
|
||||
# see these variables and will keep the safe-by-default loopback bind,
|
||||
# so it can't silently expose the viewer's bearer-authorized proxy to
|
||||
# the LAN. VIEWER_ALLOWED_HOSTS is preseeded to the Host headers that
|
||||
# `fly proxy 3113:3113` actually produces on the operator's laptop.
|
||||
if [ -n "${FLY_APP_NAME:-}" ] || [ -n "${FLY_ALLOC_ID:-}" ]; then
|
||||
: "${AGENTMEMORY_VIEWER_HOST:=::}"
|
||||
: "${VIEWER_ALLOWED_HOSTS:=localhost:3113,127.0.0.1:3113,[::1]:3113}"
|
||||
export AGENTMEMORY_VIEWER_HOST VIEWER_ALLOWED_HOSTS
|
||||
fi
|
||||
|
||||
exec gosu "$RUN_AS" agentmemory "$@"
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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 = "30s"
|
||||
method = "GET"
|
||||
path = "/agentmemory/livez"
|
||||
|
||||
[[vm]]
|
||||
size = "shared-cpu-1x"
|
||||
memory = "512mb"
|
||||
@@ -0,0 +1,35 @@
|
||||
ARG III_VERSION=0.11.2
|
||||
|
||||
FROM iiidev/iii:${III_VERSION} AS iii-image
|
||||
|
||||
FROM node:22-slim
|
||||
|
||||
ARG AGENTMEMORY_VERSION=0.9.27
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=iii-image /app/iii /usr/local/bin/iii
|
||||
|
||||
# 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} \
|
||||
TINI_SUBREAPER=1
|
||||
|
||||
COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh
|
||||
|
||||
EXPOSE 3111
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"]
|
||||
@@ -0,0 +1,136 @@
|
||||
# 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`
|
||||
- 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
|
||||
|
||||
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://<your-service>.up.railway.app/agentmemory/livez
|
||||
# {"status":"ok"}
|
||||
```
|
||||
|
||||
For an authenticated call, your client must send `Authorization: Bearer <secret>`.
|
||||
|
||||
## 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. `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
|
||||
# inside the container:
|
||||
curl http://localhost:3113
|
||||
```
|
||||
|
||||
**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 <proxy-host> -p <proxy-port>` from your laptop.
|
||||
This is the heavier path; option A is what most users will want.
|
||||
|
||||
## 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 <https://railway.com/pricing> 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 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.
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/sh
|
||||
# agentmemory first-boot entrypoint.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Then it execs the agentmemory CLI under gosu as the unprivileged
|
||||
# `node` user.
|
||||
|
||||
set -eu
|
||||
|
||||
DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}"
|
||||
HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}"
|
||||
RUN_AS="node:node"
|
||||
III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml"
|
||||
|
||||
mkdir -p "$DATA_DIR"
|
||||
chown -R "$RUN_AS" "$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
|
||||
SECRET="$(openssl rand -hex 32)"
|
||||
umask 077
|
||||
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 "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 gosu "$RUN_AS" agentmemory "$@"
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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,
|
||||
"requiredMountPath": "/data"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
ARG III_VERSION=0.11.2
|
||||
|
||||
FROM iiidev/iii:${III_VERSION} AS iii-image
|
||||
|
||||
FROM node:22-slim
|
||||
|
||||
ARG AGENTMEMORY_VERSION=0.9.27
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=iii-image /app/iii /usr/local/bin/iii
|
||||
|
||||
# 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} \
|
||||
TINI_SUBREAPER=1
|
||||
|
||||
COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh
|
||||
|
||||
EXPOSE 3111
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"]
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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
|
||||
(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`
|
||||
- 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**.
|
||||
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 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
|
||||
|
||||
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 <secret>`.
|
||||
|
||||
## 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.<region>.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.<region>.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.<region>.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 <https://render.com/pricing> 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.
|
||||
- Render runs amd64 only for web services. The Dockerfile selects the
|
||||
matching iii binary automatically via `uname -m`.
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/sh
|
||||
# agentmemory first-boot entrypoint.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Then it execs the agentmemory CLI under gosu as the unprivileged
|
||||
# `node` user.
|
||||
|
||||
set -eu
|
||||
|
||||
DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}"
|
||||
HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}"
|
||||
RUN_AS="node:node"
|
||||
III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml"
|
||||
|
||||
mkdir -p "$DATA_DIR"
|
||||
chown -R "$RUN_AS" "$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
|
||||
SECRET="$(openssl rand -hex 32)"
|
||||
umask 077
|
||||
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 "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 gosu "$RUN_AS" agentmemory "$@"
|
||||
@@ -0,0 +1,22 @@
|
||||
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: PORT
|
||||
value: "3111"
|
||||
- key: AGENTMEMORY_VERSION
|
||||
value: "0.9.27"
|
||||
- key: III_VERSION
|
||||
value: "0.11.2"
|
||||
- key: III_SDK_VERSION
|
||||
value: "0.11.2"
|
||||
Reference in New Issue
Block a user