A modern Next.js 14 app for creating and managing recipes, planning meals, and maintaining a shopping list. Optional Kroger integration lets you search products and add items to your Kroger cart.
- Recipes: create, view, edit, and delete recipes with grouped ingredients and step groups
- Meal planning: schedule recipes on a calendar; mark as made; add plan to shopping list
- Shopping list: add loose items, add all ingredients from a recipe, mark bought, bulk clear
- Auth: email/password via NextAuth (Credentials provider)
- Kroger (optional): OAuth connect, product search, add-to-cart from shopping list
- Next.js 14 (App Router), React 18
- tRPC, Prisma (PostgreSQL)
- NextAuth (credentials), Zod
- Tailwind CSS, Radix UI, Lucide Icons
- Build =
docker compose --profile prod build - Deploy =
docker compose --profile prod up -d- The
-dis critical to detach once deployed. Otherwise, Coolify will keep the container "In Progress" indefinitely.
- The
- Ensure the
Escape special characters in labels?is UNCHECKED. We need to expand an ENV in the label
There are hard coded traefik labels in the docker-compose.yaml file. These are set to work with Coolify's Traefik setup. Specifically, the endPoints are http and https for the app.
This app supports recipe images using an S3-compatible bucket (MinIO in Docker by default).
-
Copy envs
- Duplicate
.env.exampleto.envand adjust values if needed (DB, media host, etc.).
- Duplicate
-
Start services
- Using Docker Compose (app, Postgres, MinIO):
docker compose up --build
minio-initauto-creates the bucket and applies CORS driven by env (APP_ORIGINS).
-
Database
- If running without Compose DB helper, ensure Postgres is up, then:
npm run db:push
-
Run the app
npm run dev
-
Uploading images
- Open a recipe page and use the file input under "Images".
- Files upload directly to the bucket via a pre-signed URL, then the server records metadata and links to the recipe.
Notes
- For local dev via Compose, the app talks to MinIO at
http://minio:9000inside the Docker network. - To serve images over HTTPS in prod, set
NEXT_PUBLIC_MEDIA_BASE_URLto your public S3/MinIO endpoint plus bucket (e.g.https://media.example.com/recipes-media).next.config.jsderives imageremotePatternsfrom this value.
Create a .env file in the project root.
Required (core app):
DB_URL= Postgres connection string (e.g.postgresql://user:password@localhost:5432/recipes-modern)NEXTAUTH_URL= Base URL of the app (e.g.http://localhost:3000)NEXTAUTH_SECRET= Any random string in production
Required (images & object storage):
NEXT_PUBLIC_MEDIA_BASE_URL= Public base URL to your bucket (e.g.http://localhost:9000/recipes-media)S3_BUCKET= Bucket name (e.g.recipes-media)S3_ACCESS_KEY_ID= Access key (used for both MinIO root and S3 client)S3_SECRET_ACCESS_KEY= SecretS3_REGION= Region label (defaultus-east-1)- Endpoints (choose one of the following setups):
- Provide both:
S3_ENDPOINT_PUBLIC= Public browser-reachable endpoint (e.g.http://localhost:9000)S3_ENDPOINT_INTERNAL= Server-reachable endpoint (e.g.http://minio:9000in Docker)
- Or provide a single legacy:
S3_ENDPOINT= Used as a fallback if the above are not set
- Provide both:
Optional:
OPENAI_API_KEY= Enables AI recipe generationKROGER_CLIENT_ID,KROGER_CLIENT_SECRET,NEXT_REDIRECT_URI= Kroger integrationNEXT_SKIP_ADD_TO_CART= Set totrueto disable pushing items to Kroger cartAPP_ORIGINS= Comma-separated origins to allow in MinIO CORS (Compose defaults this toAPP_URL)
Notes:
- Env vars are validated at startup. To bypass during Docker builds, set
SKIP_ENV_VALIDATION=1. - Kroger and AI are optional; omit those vars if you’re not using those features.
Use the MinIO client container to verify bucket, policy, and CORS:
# List buckets (should include your S3_BUCKET)
docker compose run --rm --entrypoint /bin/sh minio-init -lc \
'mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; mc ls local'
# Show anonymous policy and CORS for the bucket
docker compose run --rm --entrypoint /bin/sh minio-init -lc \
'mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; mc anonymous get local/"$S3_BUCKET"; mc cors get local/"$S3_BUCKET"'
# Quick write test to the internal endpoint from the app container
docker compose exec -T web-app node -e "const {S3Client,PutObjectCommand}=require('@aws-sdk/client-s3');(async()=>{const endpoint=(process.env.S3_ENDPOINT_INTERNAL||process.env.S3_ENDPOINT||'').replace(/\/$/,'');const s3=new S3Client({region:process.env.S3_REGION,endpoint,forcePathStyle:true,credentials:{accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY}});await s3.send(new PutObjectCommand({Bucket:process.env.S3_BUCKET,Key:'healthcheck/test.txt',ContentType:'text/plain',Body:'ok'}));console.log('PUT_OK')})()"A docker-compose.yaml is provided to run the app and Postgres together.
docker compose up --build- Ensure
.envcontainsDB_URL,NEXTAUTH_*,NEXT_PUBLIC_MEDIA_BASE_URL, requiredS3_*values, and optionally Kroger/AI variables. - The app will be available on
http://localhost:3000.
npm run build
npm run startIn Docker (multi-step handled in the Dockerfile):
docker build -t recipes-modern .
docker run -p 3000:3000 --env-file .env recipes-modernThe container entrypoint runs npx prisma migrate deploy before next start.
- Go to
/krogerand click “Sign in to Kroger” to connect your account. - You must configure
KROGER_CLIENT_ID,KROGER_CLIENT_SECRET, andNEXT_REDIRECT_URI(must match the OAuth app’s redirect). - After connecting, you can search Kroger products and add items to your cart from the shopping list.
dev: start Next.js dev serverbuild: build the appstart: start the built appstart-prod: run Prisma migrate deploy, then start (used in Docker)db:push: push Prisma schema to DBdb:studio: open Prisma Studiolint: run Next.js ESLint
src/app— App Router pages (recipes, plan, list, auth, kroger)src/server— tRPC routers, Prisma, NextAuth configprisma/schema.prisma— database schema and migrations
-
Env validation errors: confirm required variables in
.envmatch the names above -
DB connection issues: ensure Postgres is running and
DB_URLcredentials are correct -
Auth errors: confirm
NEXTAUTH_URLmatches the URL you’re visiting andNEXTAUTH_SECRETis set in production -
Kroger errors: check OAuth app config and
NEXT_REDIRECT_URIexact match -
Images:
-
If uploads fail with 403/NoSuchBucket, ensure the bucket exists. Re-run:
docker compose up -d minio-init
-
If browser PUT is blocked by CORS, set
APP_ORIGINSin.envand re-run the init job:docker compose up -d minio-init
-
For private buckets, replace public policy with:
docker compose run --rm --entrypoint /bin/sh minio-init -lc 'mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; mc anonymous set none local/"$S3_BUCKET"'
-