The repo ships a multi-stage Dockerfile (Next.js standalone output,
runs as a non-root user) and a docker-compose.yml with a single
app service. Supabase is external — point the app at your hosted
(or self-hosted) Supabase project via env vars; no database container
is included.
-
Copy the env template and fill it in:
cp .env.local.example .env.local
-
Build and start (the
--env-fileflag is required — Compose only reads.envby default for${VAR}substitution, and this project keeps its config in.env.local):docker compose --env-file .env.local up --build -d
-
The app is served on http://localhost:3000 (publish it elsewhere with
HOST_PORT=8080in.env.local).
Use
HOST_PORT, notPORT, to move the published port.PORTis what the server listens on inside the container, andenv_filewould inject it there — leaving the app on a port the mapping and the healthcheck don't target. Compose pins it to 3000 for that reason.
NEXT_PUBLIC_*variables are inlined into the client bundle at build time. They are passed as Docker build args bydocker-compose.yml. If you change any of them, rebuild:docker compose --env-file .env.local up --build -d.- Everything else (
SUPABASE_SERVICE_ROLE_KEY,ENCRYPTION_KEY,META_APP_SECRET, …) is read at runtime from.env.localviaenv_fileand is never baked into the image — safe to change with just a container restart.
docker build \
--build-arg NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co \
--build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key \
-t wacrm .
docker run -d --env-file .env.local -e PORT=3000 -p 3000:3000 wacrm- Database migrations under
supabase/are not run by the container — apply them with the Supabase CLI as described in the README. - Received attachments are copied into the
chat-mediaSupabase Storage bucket, because Meta deletes media roughly 30 days after it arrives and the copy is the only thing that outlives that. It grows with inbound volume, so it's worth watching your project's storage quota. Turn it off per account under Settings → WhatsApp → Attachment Storage; attachments received while it's off become unviewable once Meta drops them. Files over 16 MB (the bucket's limit) are never copied. - Nothing inside the container is scheduled. If you use automation
Wait steps or flows, point an external scheduler at
GET /api/automations/cronandGET /api/flows/cronon this deployment, sending the shared secret in thex-cron-secretheader (AUTOMATION_CRON_SECRET, see.env.local.example). Both return 503 until that variable is set.