For teams

Host it yourself

rememd keeps your PRDs, session logs and notes. Run the server yourself and none of it leaves your infrastructure. There is no hosted option — this is the way it is meant to run.

One server is one organisation: a single admin, everyone else joins by invite. There is no multi-tenancy to configure.

This is a write-up of something that runs. api.rememd.com is a €4/month VPS in Helsinki set up exactly this way, and every trap below cost real time on it.

What you need

  • A machine with a public IP — a €4/month VPS is plenty
  • A domain pointed at it (api.example.com), with ports 80 and 443 open
  • About ten minutes

Caddy obtains and renews the TLS certificate automatically. You do not handle certificates.

The box

Nothing here is rememd-specific, and if you already run servers you have your own version of it: a non-root user, key-only SSH, automatic security updates, and a provider-level firewall allowing 80 and 443.

Getting the server

One static binary. CGO_ENABLED=0, so it runs on musl and glibc alike and there is no distribution matrix, and the migrations travel inside it — there is no separate schema step and nothing to keep in sync.

not yet published
curl -fsSLo rememd-backend https://github.com/<releases-repo>/releases/latest/download/rememd-backend-linux-amd64
chmod +x rememd-backend

It needs a Postgres to point at and three environment variables. Run it behind anything that terminates TLS.

DATABASE_URL=postgres://rememd:PASSWORD@localhost:5432/rememd?sslmode=disable \
REMEMD_ADMIN_TOKEN=$(openssl rand -hex 32) \
REMEMD_PUBLIC_URL=https://api.example.com \
PORT=8089 ./rememd-backend

Or with Docker, which handles TLS for you

The fuller route: Postgres, the server and Caddy together, with certificates obtained and renewed automatically. Two files, both shown here in full — there is nothing to clone.

docker-compose.yml
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: rememd
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set this in .env}
      POSTGRES_DB: rememd
    volumes:
      - rememd-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U rememd"]
      interval: 5s
      timeout: 3s
      retries: 20
    restart: unless-stopped
    # No ports: on purpose. See the trap below — a host firewall will not save you.

  server:
    image: ${REMEMD_IMAGE:?set this in .env}
    environment:
      DATABASE_URL: postgres://rememd:${POSTGRES_PASSWORD}@db:5432/rememd?sslmode=disable
      REMEMD_ADMIN_TOKEN: ${REMEMD_ADMIN_TOKEN:?set this in .env}
      REMEMD_PUBLIC_URL: https://${REMEMD_DOMAIN:?set this in .env}
      TZ: ${REMEMD_TZ:-UTC}
      PORT: "8089"
    depends_on:
      db: { condition: service_healthy }
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    ports: ["80:80", "443:443"]
    environment:
      REMEMD_DOMAIN: ${REMEMD_DOMAIN}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    depends_on: [server]
    restart: unless-stopped

volumes:
  rememd-db:
  caddy-data:
  caddy-config:
Caddyfile
{$REMEMD_DOMAIN} {
	reverse_proxy server:8089 {
		# /events is Server-Sent Events, held open for the life of a session.
		# Caddy streams without buffering by default; stating it means a future
		# tweak cannot silently start batching live updates into nothing.
		flush_interval -1
	}

	encode gzip

	# The API answers with its own JSON errors; Caddy should not dress them up.
	handle_errors {
		respond "{err.status_code} {err.status_text}"
	}
}
.env — mode 600
REMEMD_DOMAIN=api.example.com
REMEMD_ADMIN_TOKEN=<openssl rand -hex 32>
POSTGRES_PASSWORD=<openssl rand -hex 32>
REMEMD_TZ=Europe/Stockholm
REMEMD_IMAGE=<the published image>
docker compose up -d

Check it

curl https://api.example.com/healthz

The first request may take a few seconds while Caddy gets its certificate. If it hangs, DNS is not pointing here yet or 80/443 are closed — Caddy needs both to complete the ACME challenge.

/healthz reports the build: commit and build time. Checking it after a deploy is the only thing that catches a pull that did not take, which is otherwise silent and indefinite.

Connecting your own client

You are the admin, so you do not use an invite. Write the credential file:

~/.rememd/remote.json
{"server": "https://api.example.com", "token": "<the admin token>"}

Then relaunch the app. There is no mode to set.

Adding people

From the app: right-click any row in the tree → Invite → pick a level → Create invite link. Send them the link; they run the command it shows.

A grant can be a whole workspace, a product, a focus area or a single feature, and the server enforces it — a scoped member’s index is pruned before it leaves the machine, and their saves are merged back so they cannot delete what they cannot see.

The MCP connector

The server also speaks MCP over Streamable HTTP at /mcp, so it can be added to Claude as a custom connector and reached from Claude Desktop and the Claude app — not only from a terminal.

Add it once at claude.ai by URL: https://api.example.com/mcp. Claude registers itself, sends you to a consent page, and asks for a rememd access token. The connection gets its own credential, which can be revoked on its own. You cannot add a server from a phone — register on the web and it becomes available there.

Backups

A dump and a prune, on a cron. Nothing rememd-specific — it is a Postgres database and every tool you already trust works on it.

/etc/cron.d/rememd-backup, or a crontab entry
0 4 * * *  cd /srv/rememd && docker compose exec -T db pg_dump -U rememd rememd | gzip > backups/rememd-$(date +\%Y\%m\%d).sql.gz && find backups -name 'rememd-*.sql.gz' -mtime +14 -delete

Note the escaped percent signs — cron treats a bare % as a newline, which turns the command into one that silently writes an empty file.

A dump beside the database protects you from a bad migration but not from losing the machine. Copying it somewhere else is your job.

restore
gunzip -c backups/rememd-<stamp>.sql.gz | docker compose exec -T db psql -U rememd rememd

Test that on a throwaway database before you need it. An untested backup is a guess.

Upgrading

Do not build the image on the server. It compiles the Swift MCP server, which wants far more CPU and memory than a small VPS has, and the failure at the end of a long build is an unhelpful one.

docker compose pull && docker compose up -d

Migrations run at startup, so there is no separate step. Take a backup first.

The rest of what it cost