A self-hosted personal CRM for tracking your relationships — contacts, interactions, reminders, gifts, debts, journal entries — with CardDAV sync, full-text search, and a React frontend. Built on the FastAPI full-stack template and deployed to a homelab via Docker Compose behind Traefik.
Why Kindred?
We meet so many people, but keeping track of those connections isn't always easy. Kindred is built to help you stay in touch, remember the little things, and cultivate meaningful relationships. Whether you want to recall a past interaction, track a gift idea, or just get a reminder to text an old friend, Kindred provides a private, self-hosted space to keep your personal network organized without compromising your data privacy.
Quick Start
Get up and running in under 30 seconds using Docker:
docker compose up -d --build
That's it! Your Kindred instance and all necessary services (PostgreSQL, Meilisearch, Redis) will spin up automatically.
Features Showcase
| Feature | Description |
|---|---|
| Contacts & Groups | Keep everyone organized with tagging, favoriting, and relationship maps. |
| Interactions Log | Never forget a conversation. Log calls, texts, emails, and meetings. |
| Smart Reminders | Set up scheduled nudges so you don't lose touch with the people who matter. |
| Gifts & Debts | Track gifts given/received and easily manage "who owes who" scenarios. |
| Daily Journal | Maintain a freeform personal journal to reflect on your days. |
| CardDAV Sync | Native support for standard contacts syncing to your mobile devices. |
Demo & Screenshots
Captured against the seeded fake-data fixture (
just seed-fixed), 1440×900.
Dashboard — stats, "losing touch" queue, and recent interactions:
Contacts list — last-contact decay indicator, tags, and company/role:
Contact detail — timeline of interactions, notes, gifts, and life events with a relationships sidebar:
Interactions feed grouped by date with channel badges:
Reminders:
Calendar — birthdays and life-event anniversaries surfaced per day:
Journal — freeform daily entries with optional mood:
Tags:
Tech Stack
| Layer | What |
|---|---|
| Backend | FastAPI + SQLModel + Alembic, Python 3.12, uv |
| Database | PostgreSQL 18 |
| Search | Meilisearch (indexed via ARQ background tasks) |
| Queue | Redis + arq worker |
| CardDAV | Radicale mounted into the FastAPI app |
| Frontend | React + TanStack Router + TanStack Query + Vite + Bun + Tailwind + shadcn/ui |
| Auth | JWT access tokens, first-superuser bootstrap, password recovery via SMTP |
| E2E | Puppeteer (in e2e/) |
| Deploy | Docker Compose + Traefik; secrets via sops + age |
Comparison to other tools
Kindred vs. Monica
Monica is a fantastic and feature-rich personal CRM. However, it can feel heavy to host and navigate. Kindred aims for a lighter, faster, and more modern experience with a streamlined React frontend, native mobile-friendly UI, and fewer external dependencies.
Kindred vs. Notion / Obsidian
While note-taking tools are extremely flexible, they lack the structured reminders, CardDAV syncing, and specialized relationship views (like decaying contact indicators or dedicated interaction timelines) that a purpose-built CRM like Kindred provides out of the box.
Technical Documentation
Detailed Features
Shipped (frontend + backend)
- Contacts — CRUD, favorites, archive, tagging, groups, list/detail views, inline editing
- Interactions — Log calls, texts, emails, in-person meetings, notes per interaction
- Reminders — Scheduled reminders with date/time, per-contact linking
- Tags & Groups — Organize contacts, many-to-many relationships
- Journal — Freeform personal journaling with entries
- Gifts — Track gifts given/received per contact
- Debts — "I owe them" / "they owe me" tracking per contact
- Notes — Freeform notes attached to a contact
- Users & Admin — Signup, login, logout, password recovery, superuser-gated user management
- Dashboard — Stats, "losing touch" suggestions, recent interactions
Backend-only (no UI yet)
These routes are registered and tested at the API level, but have no frontend pages. Either build the UI or remove them when you decide the feature is out of scope.
addresses,pets,relationships,contact_fields— read-only rendering on the contact detail page, no add/edit UIcustom_fields— per-contact arbitrary fieldslife_events— birthdays, anniversaries, major eventsimport_export— bulk contact import/export (CSV / vCard)webhooks— outbound webhook registrations
The Copier template's items module has been removed from the API router (the model + Alembic table remain for now — drop with a dedicated migration if/when desired).
Layout
backend/ FastAPI app, Alembic migrations, pytest suite, Radicale CardDAV bridge
frontend/ React app (Vite), generated OpenAPI client in src/client/
e2e/ Puppeteer end-to-end tests (bun run e2e/*.test.ts)
docs/ architecture.md, DB_SCHEMA.md, HANDOFF.md
compose.yml Homelab production stack (kindrednet networks, Meilisearch, Redis, ARQ worker)
compose.dev.yml Dev-against-homelab overlay (bind-mounted source, --reload, frontend dev server)
.env Runtime env vars (gitignored)
.env.sops Production secrets, encrypted with sops + age
Running the stack
Production (homelab default):
docker compose up -d --build
Dev against the homelab (bind-mounted source, live reload):
docker compose -f compose.dev.yml up -d --build
First boot runs Alembic migrations and creates the admin user from FIRST_SUPERUSER / FIRST_SUPERUSER_PASSWORD in .env.
Testing
Backend (inside the running stack):
docker compose exec backend bash scripts/tests-start.shEnd-to-end (Puppeteer, requires a running stack):
corepack enable
pnpm install
pnpm run e2eSecrets
Production secrets live in .env.sops, encrypted with age. Rotate with:
EDITOR=vim sops .env.sops
Note: because
.env.sopsuses the.sopsextension (not a format sops auto-detects), you may need--input-type dotenv --output-type dotenv, or temporarily copy to a*.envpath for editing.
After rotating FIRST_SUPERUSER_PASSWORD, the change only takes effect on fresh deployments. Existing homelab databases retain the old superuser password until you manually update it (or delete and recreate the user via initial_data.py).
Release & deploy
The release flow is fully local (no GitHub Actions). Run on ares:
just release patch # cut: cliff CHANGELOG + LLM notes + tag + push + GH release just build v0.1.0 # build Dockerfile.prod, push to ghcr.io/pike00/kindred just deploy v0.1.0 # delegate to homelab apps/kindred/justfile (pg_dump + roll + healthcheck) # or all three at once: just ship patch
What each step does:
just release LEVELrunsrelease-kit cut: preflights (clean tree onmain, in sync withorigin), regenerates the unreleased section ofCHANGELOG.mdviagit-cliff, drafts the GH release body via LiteLLM (deepseek-v4-pro-cloud), opens$EDITORfor review, commitsCHANGELOG.md, tags + pushes + creates the GH release.just build TAGbuildsDockerfile.prodwith a local buildx cache and pushes:<tag>and:sha-<short>toghcr.io/pike00/kindred(the running container brand; the repo ispersonal-crm). Requires GHCR auth in~/.docker/config.json(gh token withwrite:packages).just deploy TAGdelegates to~/Documents/Homelab/apps/kindred/justfile, which does the mandatory pg-dump-before-bump, rolls the container, and runs the post-deploy healthcheck.
CHANGELOG.md is generated by git-cliff per cliff.toml; do not hand-edit it. Preview the next release section with just changelog-preview or just notes-dry-run.
Deployment tiers
Three isolated environments. Each has its own credentials, database, and Docker volumes — nothing is shared across tiers.
| Tier | Where | Domain | Stack | Database | Backups |
|---|---|---|---|---|---|
| Prod | ~/Documents/Homelab/apps/kindred/ on ares |
kindred.khanpikehome.com (tailnet-only) |
Plain docker compose, single combined image ghcr.io/pike00/kindred:vX.Y.Z |
kindred-db Postgres container, volume kindred_db_data, database kindred |
Daily pg-dump at 03:17 + Kopia to S3+B2, 30-day retention |
| Dev | ~/projects/personal-crm/ |
kindred.dev.khanpikehome.com (tailnet-only) |
compose.dev.yml overlay (bind-mounted source, live reload) |
Project-local dev-db volume, database crm |
None (ephemeral) |
| PR previews | Deferred indefinitely | — | — | — | — |
Hard isolation enforced by
- Distinct Postgres credentials per tier — secrets live only in their own
.env.sops - Distinct Docker volumes (
kindred_db_datafor prod, project-localdev-dbfor dev) — noexternal: truecross-references between tiers - Distinct Docker networks —
pikenet-internal-kindredfor prod, default bridge for dev - Distinct database names — prod is
kindred, dev iscrm - Traefik label pinning — prod compose has the domain hard-coded, dev compose has its own hard-coded dev hostname; neither uses a
${DOMAIN}variable that could claim the other's hostname
Origin
This project was scaffolded from the FastAPI full-stack template via Copier. The backend/README.md and frontend/README.md files are still mostly upstream template content — treat them as reference, not project-specific docs.
License
Kindred is source-available under the Elastic License 2.0 (ELv2).
You can: self-host for personal use, fork, modify, contribute, and run it for your own contacts.
You cannot: offer Kindred as a hosted or managed service to third parties without a commercial license. If you want to build a hosted personal CRM product on top of Kindred, get in touch.
The FastAPI full-stack template scaffold is MIT-licensed (Sebastián Ramírez); attribution is preserved in the LICENSE file.







