A self-hosted personal journaling app with three focused entry flows — anger release, gratitude practice, and creative writing with AI-generated prompts. Runs entirely in Docker; no cloud account required.
- Anger journal — one-line guided prompt, rapid-save shortcut, venting without judgment.
- Gratitude journal — randomly drawn from 100 seeded prompts stored in Postgres.
- Creative journal — persona-driven prompts generated by your local or cloud AI; full Markdown editor with live preview.
- AI prompt generation — works with Ollama, OpenAI, Gemini, or any OpenAI-compatible endpoint. Configurable in the Settings page at runtime — no container restart needed.
- Passcode gate — argon2-hashed passcode + JWT cookie; single-user by design.
- Auto-titled entries —
🤬 001,🥰 042,✍️ 007— chronological history with detail view.
Fastest path: download one file, set one password, run one command.
-
Download
docker-compose.yml:curl -O https://raw.githubusercontent.com/dad-guides/simple-journal/main/docker-compose.yml
-
Open the file and replace both occurrences of
CHANGE_MEwith a strong password:POSTGRES_PASSWORD: your-strong-password DATABASE_URL: postgres://journal:your-strong-password@db:5432/journal
-
Start the stack:
docker compose up -d
-
Open
http://localhost:3003and set your passcode.
On first boot the container auto-generates a JWT_SECRET and persists it to the journal-data volume — no manual secret generation needed.
Use this if you want to customise the source before building your own image.
-
Clone the repo:
git clone https://github.com/dad-guides/simple-journal.git cd simple-journal -
Set the password in
docker-compose.yml(same two spots as above). -
Build and start:
docker compose up -d --build
-
Open
http://localhost:3003and set your passcode.
If you already run a Postgres instance and don't want the bundled db service:
-
Set Up Postgres: Before starting, create the database on your external Postgres host:
psql -h your-postgres-host -U your-admin-user -c "CREATE USER journal WITH PASSWORD 'your-strong-password';" psql -h your-postgres-host -U your-admin-user -c "CREATE DATABASE journal OWNER journal;"
-
Copy the override example:
cp docker-compose.override.yml.example docker-compose.override.yml
-
Update
DATABASE_URLindocker-compose.ymlto point at your external host:DATABASE_URL: postgres://user:pass@your-postgres-host:5432/journal
-
docker compose up -d— the bundleddbservice is disabled automatically.
prisma migrate deploy runs on every startup and is non-destructive — it skips already-applied migrations and never drops data.
AI prompt generation is optional. Without it, the Creative tab shows a fallback prompt.
Configure the provider in the Settings page at runtime (no restart needed), or set environment variables in docker-compose.yml as operator defaults:
# Ollama (local)
AI_BASE_URL: http://your-ollama-host:11434/v1
AI_MODEL: mistral:latest
# OpenAI
# AI_BASE_URL: https://api.openai.com/v1
# AI_MODEL: gpt-4o-mini
# AI_API_KEY: sk-...
# Gemini
# AI_BASE_URL: https://generativelanguage.googleapis.com/v1beta/openai
# AI_MODEL: gemini-2.0-flash
# AI_API_KEY: AI...When the AI provider is offline the app saves a fallback prompt and the UI notes it clearly.
cd journal-web
cp ../.env.example ../.env # fill in DATABASE_URL and other vars
npm install
npm run dev # http://localhost:3003Ensure a Postgres instance is reachable at the DATABASE_URL you set (the bundled Docker Compose db service works fine).
| Command | Description |
|---|---|
npm run dev |
Next.js dev server on port 3003 |
npm run build |
Production build |
npm run lint |
ESLint with Next.js config |
npm test |
Vitest unit tests |
npm run db:migrate |
prisma migrate deploy |
npm run db:seed |
Upsert counters, user, prompts, personas |
| Component | Purpose |
|---|---|
journal-web |
Next.js App Router — UI + API routes |
db (postgres:16-alpine) |
Stores users, entries, prompts, personas, counters |
journal-data volume |
Persists auto-generated JWT_SECRET across restarts |
| Symptom | Fix |
|---|---|
| Container exits immediately | Check docker compose logs journal-web — most likely DATABASE_URL is wrong or db isn't healthy yet |
Can't log in after recreating the journal-data volume |
A new JWT_SECRET was generated; existing cookies are invalid — just log in again |
| AI prompts show "fallback" badge | AI provider unreachable; check AI_BASE_URL and model name in Settings or docker-compose.yml |
| No gratitude prompts | Re-run the seed: docker compose exec journal-web node prisma/seed.js |
| No creative personas | Same as above |