---
title: "Deploy Your Chatfuel Wizard App: Vercel, Node.js or Docker"
description: "Deploy the Chatfuel wizard app to Vercel, your own Node.js server or Docker, decide who may reach it, and fix the two setups the server refuses to run."
canonical_url: https://chatfuel.com/docs/wizard/deploy
markdown_url: https://chatfuel.com/docs/wizard/deploy.md
last_updated: 2026-10-27
lang: en
site: https://chatfuel.com/docs
llms_txt: https://chatfuel.com/docs/llms.txt
---

# Deploy Your Chatfuel Wizard App: Vercel, Node.js or Docker

Deploy the Chatfuel wizard app to Vercel, your own Node.js server or Docker, decide who may reach it, and fix the two setups the server refuses to run.

The app the [Chatfuel wizard](https://chatfuel.com/docs/wizard/quickstart) writes runs on Vercel, on your own Node.js server or in Docker, from the same code. The host decides who manages TLS and how long a WebSocket stays open; it does not decide who may use the app. That second question comes first, because the app talks to Chatfuel with your personal API token: whoever reaches the app acts with that token's access unless the `auth` module asks them to sign in.

## How to deploy to Vercel [#how-to-deploy-to-vercel]

The wizard offers a Vercel deploy at the end of its run. Later, run it from the app directory:

```bash
npm run deploy
```

The deploy script (`scripts/deploy-vercel.mjs` in your app) uses the Vercel CLI: it checks that you are signed in, links the project, pushes the variables from your `.env` to the Vercel project, deploys and asks the new deployment whether it came up configured. Your local `.env` file is never uploaded; the variables live in the Vercel project.

* **Requests:** the proxy runs as a Vercel function behind a rewrite of `/chatfuel/*`.
* **Live updates:** a WebSocket lasts as long as the function's maximum duration, then the app reconnects on its own.
* **Path:** the app is served from the domain root; a sub-path such as `/app/` needs a Node.js server or Docker.

## How to run the app on your own Node.js server [#how-to-run-the-app-on-your-own-nodejs-server]

```bash
npm run build && npm start
```

`npm run build` builds the browser app and the server. `npm start` runs one Node.js server that serves the built app and the `/chatfuel/*` proxy on port 3000 (set `PORT` to change it, `HOST` to choose the interface). Put a reverse proxy with TLS, such as nginx or Caddy, in front of it.

## How to run the app in Docker [#how-to-run-the-app-in-docker]

The app ships with a `Dockerfile` that builds the app and starts the same Node.js server on port 3000. Pass the public `VITE_*` values as build arguments and every secret at runtime, for example with `docker run --env-file .env`. Never pass `CHATFUEL_TOKEN` or another secret as a build argument: build arguments stay readable in the image.

## Who may reach the deployed app [#who-may-reach-the-deployed-app]

Without the `auth` module the app has no sign-in: the proxy forwards every request it receives under your personal API token. Anyone who opens the URL can read conversations and contacts, send messages as the bot and change settings, even though the token itself never leaves the server. That setup suits a tool for your own team, kept off the public internet:

* on a VPN, a Tailscale or WireGuard network, or behind an office IP allowlist;
* behind your host's access control, such as Vercel Deployment Protection, nginx `auth_basic` or Cloudflare Access;
* on `localhost` only, which is what `npm run dev` gives you.

An app that customers or staff open from the internet needs the `auth` module: each person signs in with your Supabase project before the proxy forwards anything. Add it with `npx @chatfuel/wizard --embed`. [Wizard modules](https://chatfuel.com/docs/wizard/modules) describes `auth`.

## Which setups the server refuses to run [#which-setups-the-server-refuses-to-run]

The server refuses two setups instead of warning about them. `npm start` prints `REFUSING TO SERVE` and does not start, the Vite dev server with `--host` stops before it mounts the proxy, and the Vercel function answers `503` with the code `ProxyRefusedToServe` to everything except the health check.

* **No sign-in on a host that is not loopback.** The server listens on all interfaces by default, and without `auth` anyone who can reach it drives your token. Fix it one of three ways: install the `auth` module, bind to loopback with `HOST=127.0.0.1`, or, when something in front of the app already authenticates every visitor, set `CHATFUEL_OPEN_PROXY=1` to confirm the open setup is intended.
* **`ALLOWED_ORIGINS=*` on a host that is not loopback**, with or without sign-in. Set `ALLOWED_ORIGINS` to the origins that serve the app, separated by commas, for example `https://app.example.com`. `CHATFUEL_OPEN_PROXY` does not cover this case.

## How to check a deployment and protect the admin panel [#how-to-check-a-deployment-and-protect-the-admin-panel]

`/chatfuel/healthz` answers on every host. It returns `{"ok":true}` with status `200` when the server is configured, or `{"ok":false}` with `503` when the configuration has a problem. An HTML page in reply means the path is wrong.

The `admin` module adds a panel at `/admin` that can change the whole account behind the token. `ADMIN_PASSWORD` is its only protection and must be at least 16 characters, or the panel refuses to run. With the `auth` module installed, also set `SUPABASE_SERVICE_ROLE_KEY` so every server instance shares one count of wrong passwords; on Vercel, where any warm instance may answer, the count is otherwise kept per instance.

## Rules every host must follow [#rules-every-host-must-follow]

* **Unknown paths answer with `index.html`.** Every screen has a real address, such as `/deals`, and a reload must return to it. The Node.js server and the shipped `vercel.json` already do this; on nginx use `try_files $uri /index.html;`.
* **`VITE_*` variables are fixed at build time**, every other variable is read by the server at runtime. After changing a `VITE_*` value, rebuild. A browser app built with Supabase settings and a server started without them, or the other way round, fails at sign-in with `ProxyAuthMisconfigured` or `AuthSessionRequired`.
* **Secrets stay on the server.** Nothing named `VITE_*` may hold a secret, because those values are compiled into the browser code.

## Common issues [#common-issues]

### REFUSING TO SERVE: open mode on a host that is not loopback [#refusing-to-serve-open-mode-on-a-host-that-is-not-loopback]

The server runs without the `auth` module on an address others can reach. Install `auth`, set `HOST=127.0.0.1`, or set `CHATFUEL_OPEN_PROXY=1` when your own access control already sits in front of the app.

### REFUSING TO SERVE: ALLOWED\_ORIGINS is '\*' [#refusing-to-serve-allowed_origins-is-]

Replace `*` with the exact origins of your app, for example `ALLOWED_ORIGINS=https://app.example.com`, and restart the server.

### ADMIN\_PASSWORD must be at least 16 characters. Set a longer one and restart. [#admin_password-must-be-at-least-16-characters-set-a-longer-one-and-restart]

The admin panel refuses a short password. Set a longer `ADMIN_PASSWORD` in the server environment, or in the Vercel project, and restart or redeploy.
