Norish is a real-time, household-first recipe app for planning meals, sharing groceries, and cooking together.
- Norish
- Nora
The vision for Norish is a shared recipe app built for friends, families, and households that want one collaborative recipe catalog.
The name comes from Nora (our dog) + dish. Coincidentally, it also sounds like "nourish".
Norish started because we wanted a cooking app that felt lightweight, collaborative, and truly real-time while shopping and planning together.
Norish is intentionally minimal. It focuses on practical day-to-day planning.
- Easy recipe import from URL, with AI fallback if configured.
- Video recipe import from YouTube Shorts, Instagram Reels, TikTok, and more (requires AI provider).
- Image recipe import from screenshots/photos of recipes (requires AI provider).
- Nutritional information generation (requires AI provider).
- Allergy detection and warnings for recipe ingredients (detection requires AI provider).
- Unit conversion metric <-> US (requires AI provider).
- Recurring groceries via NLP or manual setup.
- Real-time sync of recipes, groceries, and meal planning data.
- Households with shared groceries and planning.
- CalDAV sync for calendar integration.
- Mobile-first design with light/dark mode support.
- Authentication options: OIDC, OAuth providers, and first-time password auth fallback.
- Admin settings UI for runtime configuration.
- Permission policies for recipe visibility/edit/delete scopes.
- Internationalization (i18n) currently supporting EN, NL and DE
Note: AI feature speed can vary by provider, model, and region.
services:
norish:
image: norishapp/norish:latest
container_name: norish-app
restart: always
ports:
- "3000:3000"
user: "1000:1000"
volumes:
- norish_data:/app/uploads
environment:
AUTH_URL: http://norish.example.com
DATABASE_URL: postgres://postgres:norish@db:5432/norish
MASTER_KEY: <32-byte-base64-key> # openssl rand -base64 32
CHROME_WS_ENDPOINT: ws://chrome-headless:3000
REDIS_URL: redis://redis:6379
# Optional
# NEXT_PUBLIC_LOG_LEVEL: info
# TRUSTED_ORIGINS: http://192.168.1.100:3000,https://norish.example.com
# YT_DLP_BIN_DIR: /app/bin
# First-user auth setup (choose one)
# PASSWORD_AUTH_ENABLED=false
# OIDC_NAME: NoraId
# OIDC_ISSUER: https://auth.example.com
# OIDC_CLIENT_ID: <client-id>
# OIDC_CLIENT_SECRET: <client-secret>
# OIDC_WELLKNOWN: https://auth.example.com/.well-known/openid-configuration
# GITHUB_CLIENT_ID: <github-client-id>
# GITHUB_CLIENT_SECRET: <github-client-secret>
# GOOGLE_CLIENT_ID: <google-client-id>
# GOOGLE_CLIENT_SECRET: <google-client-secret>
healthcheck:
test:
test:
[
"CMD-SHELL",
'node -e "require(''http'').get(''http://localhost:3000/api/health'', r => process.exit(r.statusCode===200?0:1))"',
]
interval: 1m
timeout: 15s
retries: 3
start_period: 1m
depends_on:
- db
- redis
db:
image: postgres:17-alpine
container_name: norish-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: norish
POSTGRES_DB: norish
volumes:
- db_data:/var/lib/postgresql/data
chrome-headless:
image: zenika/alpine-chrome:latest
container_name: chrome-headless
restart: unless-stopped
command:
- "--no-sandbox"
- "--disable-gpu"
- "--disable-dev-shm-usage"
- "--remote-debugging-address=0.0.0.0"
- "--remote-debugging-port=3000"
- "--headless"
redis:
image: redis:8.4.0
container_name: norish-redis
restart: unless-stopped
volumes:
- redis_data:/data
volumes:
db_data:
norish_data:
redis_data:The first user to sign in becomes server owner + server admin. After first sign-in:
- User registration is disabled automatically.
- Ongoing server settings are managed in
Settings -> Admin.
Server owners/admins can manage:
- Registration policy.
- Permission policies for recipe view/edit/delete.
- Auth providers (OIDC, GitHub, Google).
- OIDC claim mapping for admin role assignment + household auto-join.
- Content detection settings (units, content indicators, recurrence config).
- AI + video processing settings.
- System scheduler and server restart actions.
env-config-server.ts is the source of truth for runtime env vars.
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgres://user:pass@db:5432/norish |
MASTER_KEY |
32+ character key for encryption derivation | openssl rand -base64 32 |
| Variable | Description | Typical value |
|---|---|---|
AUTH_URL |
Public URL used for callbacks and links | https://norish.example.com |
CHROME_WS_ENDPOINT |
Playwright CDP WebSocket endpoint for scraping | ws://chrome-headless:3000 |
REDIS_URL |
Redis connection URL for events and jobs | redis://redis:6379 |
| Variable | Description | Default |
|---|---|---|
NODE_ENV |
Runtime environment | development |
HOST |
Server bind address | 0.0.0.0 |
PORT |
Server port | 3000 |
AUTH_URL |
Public URL for auth callbacks and links | http://localhost:3000 |
TRUSTED_ORIGINS |
Comma-separated additional trusted origins | (empty) |
UPLOADS_DIR |
Upload storage directory | ./uploads |
CHROME_WS_ENDPOINT |
Playwright CDP WebSocket endpoint | ws://chrome-headless:3000 |
REDIS_URL |
Redis connection URL | redis://localhost:6379 |
ENABLE_REGISTRATION |
Allow new-user registration | false |
AI_ENABLED |
Enable AI features globally | false |
Configure one provider for initial sign-in; after that, use Settings -> Admin.
Provider callback URLs:
| Provider | Callback URL |
|---|---|
| OIDC | https://example.norish-domain.com/api/auth/oauth2/callback/oidc |
| GitHub | https://example.norish-domain.com/api/auth/callback/github |
https://example.norish-domain.com/api/auth/callback/google |
| Variable | Description | Default |
|---|---|---|
PASSWORD_AUTH_ENABLED |
Enable email/password auth bootstrap | Auto |
OIDC_NAME |
Display name for OIDC provider | (empty) |
OIDC_ISSUER |
OIDC issuer URL | (empty) |
OIDC_CLIENT_ID |
OIDC client id | (empty) |
OIDC_CLIENT_SECRET |
OIDC client secret | (empty) |
OIDC_WELLKNOWN |
OIDC well-known URL (derived from issuer if omitted) | Derived |
GITHUB_CLIENT_ID |
GitHub OAuth client id | (empty) |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret | (empty) |
GOOGLE_CLIENT_ID |
Google OAuth client id | (empty) |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret | (empty) |
These are only used when claim mapping is enabled.
| Variable | Description | Default |
|---|---|---|
OIDC_CLAIM_MAPPING_ENABLED |
Enable claim-based role and household assignment | false |
OIDC_SCOPES |
Additional OIDC scopes (comma-separated) | (empty) |
OIDC_GROUPS_CLAIM |
Claim name containing group memberships | groups |
OIDC_ADMIN_GROUP |
Group name that grants server admin role | norish_admin |
OIDC_HOUSEHOLD_GROUP_PREFIX |
Prefix for household auto-join groups | norish_household_ |
| Variable | Description | Default |
|---|---|---|
AI_PROVIDER |
AI provider | openai |
AI_ENDPOINT |
Custom provider endpoint | (empty) |
AI_MODEL |
Default model | gpt-5-mini |
AI_API_KEY |
API key for provider | (empty) |
AI_TEMPERATURE |
Generation temperature | 1.0 |
AI_MAX_TOKENS |
Maximum tokens for model responses | 10000 |
| Variable | Description | Default |
|---|---|---|
VIDEO_PARSING_ENABLED |
Enable video parsing pipeline | false |
VIDEO_MAX_LENGTH_SECONDS |
Maximum accepted video length | 120 |
YT_DLP_VERSION |
yt-dlp version used by downloader | 2025.11.12 |
YT_DLP_BIN_DIR |
Folder containing yt-dlp binary | env-dependent |
TRANSCRIPTION_PROVIDER |
Transcription provider | disabled |
TRANSCRIPTION_ENDPOINT |
Transcription endpoint (local/custom providers) | (empty) |
TRANSCRIPTION_API_KEY |
Transcription API key | (empty) |
TRANSCRIPTION_MODEL |
Transcription model | whisper-1 |
| Variable | Description | Default |
|---|---|---|
UNITS_JSON |
Override units dictionary | (empty) |
CONTENT_INDICATORS |
Override recipe-content indicator configuration | (empty) |
CONTENT_INGREDIENTS |
Override ingredient-content configuration | (empty) |
| Variable | Description | Default |
|---|---|---|
SCHEDULER_CLEANUP_MONTHS |
Cleanup retention period in months | 3 |
MAX_AVATAR_FILE_SIZE |
Max avatar upload size (bytes) | 5242880 |
MAX_IMAGE_FILE_SIZE |
Max image upload size (bytes) | 10485760 |
MAX_VIDEO_FILE_SIZE |
Max video upload size (bytes) | 104857600 |
| Variable | Description | Default |
|---|---|---|
DEFAULT_LOCALE |
Instance default locale | en |
ENABLED_LOCALES |
Comma-separated list of enabled locales | (all enabled) |
# Clone the repository
git clone https://github.com/mikeve97/norish.git
cd norish
# Install dependencies
pnpm install
# Create your environment file
cp .env.example .env.local
# Start required services (for example via Docker)
# docker run -d --name norish-db -e POSTGRES_PASSWORD=norish -e POSTGRES_DB=norish -p 5432:5432 postgres:17-alpine
# docker run -d --name norish-redis -p 6379:6379 redis:7-alpine
# Run the app
pnpm run dev| Command | Description |
|---|---|
pnpm run dev |
Start development server with hot reload |
pnpm run build |
Full production build (Next.js + server + service worker) |
pnpm run test |
Run tests in watch mode |
pnpm run test:run |
Run tests once |
pnpm run test:coverage |
Run tests with coverage report |
pnpm run lint |
Lint and auto-fix issues |
pnpm run lint:check |
Lint TypeScript files |
pnpm run format |
Format files with Prettier |
pnpm run format:check |
Check formatting without changing files |
- Next.js 16
- Tailwind CSS 4
- HeroUI
- Framer Motion
- TanStack Query
- Node.js custom server
- tRPC
- Better Auth
- Pino
- Redis
- BullMQ
- PostgreSQL
- Drizzle ORM
- OpenAI SDK
- Playwright
- yt-dlp
- Sharp
- FFmpeg
- Vitest
Norish is licensed under AGPL-3.0.
Last but not least, a picture of our lovely dog Nora:

