Skip to content
ChatfuelSDK
Deployment

Docker

The shipped Dockerfile, and the build-arg split that puts a secret in the image if you get it wrong.

A build arg ends up in the image. That is the whole reason the split exists: VITE_* values are compiled into the client bundle and have to be --build-args, and everything the server reads — CHATFUEL_TOKEN, SUPABASE_SERVICE_ROLE_KEY, CHATFUEL_WORKSPACE_ID — is runtime-only and must never be one.

Build and run

docker build -t chatfuel-app \
  --build-arg VITE_CHATFUEL_WORKSPACE_ID=... \
  --build-arg VITE_SUPABASE_URL=... \
  --build-arg VITE_SUPABASE_ANON_KEY=... \
  --build-arg VITE_APP_NAME=... \
  --build-arg VITE_APP_LOGO=... .
docker run -p 3000:3000 --env-file .env chatfuel-app

Which variable goes where

VariableBuild argRuntime env
VITE_CHATFUEL_WORKSPACE_ID, VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, VITE_APP_NAME, VITE_APP_LOGOyesyes — the proxy reads them again at request time
CHATFUEL_TOKEN, SUPABASE_SERVICE_ROLE_KEYneveryes
CHATFUEL_WORKSPACE_IDneveryes
CHATFUEL_API_BASE, PORT, PUBLISHING_SECRET, PUBLIC_URL, ADMIN_PASSWORDneveryes

The VITE_* row is the one people get half right: passing them as build args alone produces a client that works and a proxy that does not, because the proxy reads the same values at runtime. Pass them both ways, with the same values.

A --build-arg is in the image. A secret passed that way stays in it whatever the container's runtime environment says, and nothing at run time takes it back out.

What the image is

Two stages on node:22-alpine. The builder declares the five VITE_* args, puts them in its environment, installs from package.json and the lockfile with npm ci, copies the source and runs npm run build. The runtime stage installs with npm ci --omit=dev, copies dist/ and server/dist/ across from the builder, drops to the node user, exposes 3000 and runs node server/dist/entry.js.

ws, undici and https-proxy-agent are runtime dependencies rather than dev ones for exactly that last step: under npm ci --omit=dev the production server still has to relay WebSockets and honour HTTPS_PROXY.

The environment at run time

--env-file .env is one way in; variables set by your orchestrator are another. If a .env ends up inside the image next to the app it is loaded on start, and real environment variables win over it — so an image built with a file in it still takes its configuration from the platform.

PORT defaults to 3000, which is what EXPOSE, the runtime stage's own ENV and the -p 3000:3000 above all assume. The runtime stage also sets NODE_ENV=production.

Sub-paths work here

Unlike Vercel — see Serving from a sub-path. BASE_PATH goes in the runtime environment like any other server variable. VITE_BASE_PATH is not one of the five args the shipped Dockerfile declares, so add an ARG and an ENV line for it beside the others.

On this page