heycapy is open source, so you can run it on your own server for free. it’s one docker container plus caddy for https.
start it
you need a server with docker and a domain pointing at it.
git clone https://github.com/heycapy/heycapy
cd heycapy
cp .env.example .env
fill in .env (more below), put your domain in the Caddyfile, then:
docker compose up -d
caddy gets the https certificate by itself. the database lives in a docker volume.
the settings that matter
everything is in .env.example, with a note on each line. the main ones:
| setting | what it’s for |
|---|---|
JWT_SECRET, ENCRYPTION_KEY | required. make them with openssl rand -base64 32 and openssl rand -hex 32 |
APP_URL | your public address, like https://capy.example.com |
ADMIN_EMAILS | who sees the system tab and gets alerts |
leave HOSTED out. that one’s only for app.heycapy.xyz.
heycapy sends sign in codes and email reminders. pick one of two ways:
- resend: put your key in
RESEND_API_KEY, and the sender inEMAIL_FROM. - your own mail server: fill in
SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASSandSMTP_FROM. port 587 is the usual one. 465 is ssl, and heycapy switches that on by itself.
with neither, the sign in code shows on the login screen. fine for trying it on your laptop, not for a real server.
each user can also send their own reminders through their own mail server, in tweaks → notifications → email.
telegram
make a bot with @BotFather and put its token in TELEGRAM_BOT_TOKEN. APP_URL has to be a public https address, because telegram sends messages to your server there. after that, connecting works like in the telegram guide.
ai
AI_PROVIDER, AI_MODEL and AI_API_KEY set the ai for users without their own key. any user can still add their own key in tweaks → ai, see the ai guide. ollama works here too, with OLLAMA_URL.
push and ntfy
push notifications work out of the box over https. ntfy works with ntfy.sh or your own ntfy server, set per user in the app.
keeping it running
- backups: every night the database is copied to a
backups/folder next to it, keeping the last 7. copy that folder off the server too. - health:
/api/healthfor your platform,/api/health/schedulerfor an uptime monitor. it answers503when reminders are stuck. - updates: back up first, then see upgrading below.
upgrading
back up first, then move to the new version:
docker compose stop heycapy
docker compose cp heycapy:/data ./heycapy-backup
git fetch --tags
git checkout $(git describe --tags --abbrev=0 origin/main)
docker compose up -d --build
stopping first matters: while heycapy runs, recent changes sit in a separate heycapy.db-wal file that a copy can miss. the database updates itself on start. what changed is in the changelog.
restoring a backup
every night heycapy saves a copy of the database in backups/. each file is complete on its own. you also need the .env from back then: without the same ENCRYPTION_KEY, the keys and tokens saved in the app can’t be read. a different JWT_SECRET only signs everyone out.
-
stop heycapy and get the backup file next to your
docker-compose.yml. if it’s still on the server:docker compose stop heycapy docker compose cp heycapy:/data/backups ./heycapy-backupson a new server, put your off-server copy there instead and set up
.envand theCaddyfileas in the start section. -
restore it, with the name of the file you want:
docker compose run --rm --no-deps --user root \ -v "$PWD/heycapy-backups/heycapy-2026-10-04.db:/restore.db:ro" \ --entrypoint sh heycapy -c ' mkdir -p /data/before-restore mv /data/heycapy.db* /data/before-restore/ 2>/dev/null cp /restore.db /data/heycapy.db chown nextjs:nodejs /data/heycapy.db' -
start it again and sign in:
docker compose up -d
the database you replaced is kept in before-restore/ inside the volume, so a wrong pick can be undone. delete that folder once you’re sure.
don’t copy the file in with docker compose cp instead: the old heycapy.db-wal stays behind and is applied on top, which silently undoes the restore, and the file ends up owned by the wrong user so heycapy can’t write to it.
restoring after a bad upgrade: git checkout the previous version first, then restore, then docker compose up -d --build. restoring a backup made by a newer version into an older one isn’t supported.