Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jAtInn71/chatwoot-costom/llms.txt

Use this file to discover all available pages before exploring further.

Chatwoot Custom ships as a single Docker image built by a multi-stage Dockerfile. Stage 1 clones upstream Chatwoot, overlays all custom frontend files, and runs a Vite build. Stage 2 starts from the official Chatwoot Rails image and overlays all custom backend files on top of the compiled frontend output. The build.sh script is the recommended entry point for local and CI builds.

build.sh

Usage:
./build.sh [agent_id] [voice_id] [agent_name] [tag]
All arguments are optional. Their defaults:
ArgumentDefaultPurpose
agent_idagent_6601kc1fqeecfc88s7d52jde0syqLegacy fallback agent ID baked into the bundle
voice_id(empty)ElevenLabs voice ID (legacy fallback)
agent_nameAI AssistantDisplay name (legacy fallback)
taglatestDocker image tag
The script passes these as Docker build arguments and tags the output image chatwoot-custom:<tag>:
docker build \
  --build-arg VITE_ELEVENLABS_AGENT_ID="$AGENT_ID" \
  --build-arg VITE_ELEVENLABS_VOICE_ID="$VOICE_ID" \
  --build-arg VITE_ELEVENLABS_AGENT_NAME="$AGENT_NAME" \
  -t chatwoot-custom:$IMAGE_TAG \
  -f Dockerfile \
  .
Examples:
# Build with defaults
./build.sh

# Build with a specific agent and version tag
./build.sh agent_xyz voice_abc "Support Bot" v1.2.0

ElevenLabs env vars are legacy fallbacks

VITE_ELEVENLABS_AGENT_ID, VITE_ELEVENLABS_VOICE_ID, and VITE_ELEVENLABS_AGENT_NAME are Vite build arguments — they are inlined into the widget JavaScript bundle at build time via import.meta.env. Changing the agent ID at runtime without rebuilding has no effect. These build args are legacy fallbacks only. Voice agent configuration now lives exclusively in the per-inbox dashboard settings (voice_agent_provider, voice_agent_api_key, voice_agent_config_data, elevenlabs_agent_id). The widget fetches this config on every panel open via GET /api/v1/widget/conversations/inbox_config, so dashboard changes take effect on the next bubble click without any rebuild.
For new deployments, skip the build-arg approach entirely. Build with ./build.sh (defaults), then configure your agent from the Chatwoot dashboard under Settings → Inboxes → [your inbox] → Configuration → Voice Agent. The build args exist only for edge-case fallbacks and backwards compatibility.

Dockerfile overview

Stage 1 — Node.js / Vite build

FROM node:18-bullseye-slim AS node-builder
Debian (glibc) is used instead of Alpine (musl). Vite and esbuild segfault (exit 139) under heavy minification load on Alpine in memory-constrained CI runners. glibc provides a much more stable V8 runtime for builds of this size. The stage:
  1. Installs git, python3, build-essential, and pnpm.
  2. Clones upstream Chatwoot with --depth 1.
  3. Runs pnpm install --frozen-lockfile.
  4. Copies all custom/widget/ and custom/dashboard/ files over the cloned source tree.
  5. Runs vite build --config vite.config.ts --minify esbuild.
The COPY directives in Stage 1 (excerpt):
COPY custom/widget/components/ElevenLabsVoiceButton.vue \
     app/javascript/widget/components/ElevenLabsVoiceButton.vue
COPY custom/widget/components/ChatInputWrap.vue \
     app/javascript/widget/components/ChatInputWrap.vue
COPY custom/widget/store/modules/voiceAgentConfig.js \
     app/javascript/widget/store/modules/voiceAgentConfig.js
COPY custom/dashboard/ConfigurationPage.vue \
     app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue
# ... (17 total frontend files)

Stage 2 — Rails production image

FROM chatwoot/chatwoot:latest
Copies the complete Vite build output from Stage 1 and then overlays all backend custom files:
# Frontend: copy full public build output from Stage 1
COPY --from=node-builder /chatwoot-src/public /app/public

# Backend overlay
COPY custom/backend/models/web_widget.rb \
     /app/app/models/channel/web_widget.rb
COPY custom/backend/controllers/conversations_controller.rb \
     /app/app/controllers/api/v1/widget/conversations_controller.rb
COPY custom/backend/controllers/inboxes_controller.rb \
     /app/app/controllers/api/v1/accounts/inboxes_controller.rb
COPY custom/backend/migrations/20260408000001_add_elevenlabs_to_channel_web_widgets.rb \
     /app/db/migrate/20260408000001_add_elevenlabs_to_channel_web_widgets.rb
COPY custom/backend/migrations/20260409000001_add_voice_agent_config_to_channel_web_widgets.rb \
     /app/db/migrate/20260409000001_add_voice_agent_config_to_channel_web_widgets.rb
# ... (routes, views, concerns)
Do not copy raw .vue files into Stage 2. The Dockerfile comment says it explicitly: “Do NOT copy raw Vue files here — they will override the Vite-built assets and break the application.” All frontend customizations are compiled in Stage 1 and included via the COPY --from=node-builder /chatwoot-src/public /app/public line.

Memory requirements

The Vite build is memory-intensive. The Dockerfile sets the following environment variables in Stage 1 to prevent OOM kills:
ENV NODE_OPTIONS="--max-old-space-size=6144 --max-semi-space-size=128 --max-http-header-size=16384"
ENV UV_THREADPOOL_SIZE=4
ENV GOMAXPROCS=2
The rationale:
  • --max-old-space-size=6144 — raises the V8 heap. Total RSS during minification spikes past 4 GB on this codebase; 3 GB was causing SIGSEGV (exit 139) in CI.
  • --max-semi-space-size=128 — reduces GC churn in the young-generation heap.
  • UV_THREADPOOL_SIZE=4 — caps libuv workers to avoid forking too many native threads on memory-tight runners.
  • GOMAXPROCS=2 — limits esbuild parallel workers, the biggest source of OOM during minification.
The build requires approximately 8 GB of free RAM. If it fails with SIGSEGV or exit code 139, use a machine with more memory or uncomment the no-minify fallback line in the Dockerfile:
# Uncomment this line and comment out the minify line above:
RUN node_modules/.bin/vite build --config vite.config.ts
Minification can be applied as a separate post-step if needed.

Local development workflow

1

Edit custom files

Make changes inside custom/widget/, custom/backend/, or custom/dashboard/. Do not touch any file outside custom/.
2

Rebuild the image

./build.sh
Or with a specific tag to avoid overwriting your last-known-good image:
./build.sh "" "" "" dev
3

Bring up the stack

docker compose down && docker compose up -d
4

Run database migrations

On first run or after adding new migrations:
docker compose exec rails bundle exec rails db:migrate

Pushing to a registry

After a successful build:
docker tag chatwoot-custom:latest <registry>/chatwoot-custom:latest
docker push <registry>/chatwoot-custom:latest
Or with a version tag:
docker tag chatwoot-custom:v1.2.0 <registry>/chatwoot-custom:v1.2.0
docker push <registry>/chatwoot-custom:v1.2.0
Replace <registry> with your container registry hostname (e.g., ghcr.io/your-org, registry.example.com).

Build docs developers (and LLMs) love