Web container booted but every page logged:
Error: Cannot find module 'rrule'
at /app/packages/shared/dist/rrule.js
Next's standalone tracer copied packages/shared/dist/*.js into the
standalone output but didn't follow their require("rrule") chain
into packages/shared/node_modules — pnpm's symlinked dep layout
isn't always followable by the tracer. Same risk for any other
shared/db transitive dep (luxon, cron-parser, drizzle-orm, pg,
bcryptjs).
Copy packages/shared/node_modules and packages/db/node_modules into
the runtime stage explicitly so the standalone require chain
resolves. Bot Dockerfile already ships full node_modules so it's
unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setting the route's `dynamic = "force-dynamic"` only stops Next from
calling the GET handler — not from evaluating the route module. The
bundled route.js inlines lib/db.ts's top-level
createClient(env.DATABASE_URL); next build's "Collecting page data"
pass imports the bundle, env access fires, and Zod throws because
the build container has no DATABASE_URL.
Same story for AUTH_SECRET via actions/auth.ts.
Export both as placeholders inside the RUN layer. pg.Pool is lazy
(stores URL, only connects on first query) and AUTH_SECRET only
matters at sign/verify time, so neither placeholder runs during
build. Each Dockerfile RUN is its own shell — nothing leaks into
the runtime image.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bot + web Dockerfiles tried to addgroup -g 1000 app on top of
node:22-alpine, which already ships a `node` group at gid 1000.
Build aborted at runtime stage 5/5 with:
addgroup: gid '1000' in use
Drop the addgroup/adduser pair on both images and just chown +
USER node onto the existing node user. Same hardening posture
(non-root, no shell login on the runtime image), one less moving
part. The compose dev overlay's `user: ${HOST_UID:-1000}:${HOST_GID:-1000}`
matches uid 1000 either way.
Plus:
- New docker-compose.portainer.yml: pulls cm-whatsapp-{bot,web}
from gitea.04080616.xyz/yiekheng instead of building from
source. Named volumes for sessions / media so the operator
doesn't need shell access to manage state. Healthchecks on
both services so Portainer's UI surfaces unhealthy containers.
- New docs/deploy-portainer.md walking through registry auth,
stack creation, env vars, migrations, first sign-in, future
redeploys, rollbacks.
- README links the Portainer guide alongside the dev path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
robots.ts + metadata.robots blocks indexing.
serverActions.allowedOrigins gates cross-origin Server Action posts.
Bot + web Dockerfiles add a non-root 'app' user (uid 1000) with
chmod 700 on /data/sessions.
sendTestAction grows a per-group rate limit (3/60s).
resumeReminderRunAction + cancelReminderRunAction get a per-IP
rate limit (30/10s).
.env.example documents every required key.
packages/db/src/scripts/{set-password,create-user}.ts + thin shell
wrappers in scripts/ — first admin sets their password via
./scripts/set-password.sh admin before signing in.