Deploy
Your app, on your own domain, at an address you hand to your customers. The same source runs on Vercel, on a server of your own and in a container.
One command, from a terminal to a live URL
Every step is idempotent, so shipping an update later is the same command again.
- 01
The Vercel CLI
Yours if it is installed, fetched with npx for the run if it is not.
- 02
Signed in
The browser login, or VERCEL_TOKEN in a pipeline and nothing opens.
- 03
A project of your own
Named by you, because the name is also the address the app answers on.
- 04
Your environment, pushed
Every variable your .env fills in, into production and preview, secrets write-only.
- 05
Built and deployed
The build log on screen, and the line that says why a failure failed at the bottom.
- 06
A URL that works
Each address tried until one opens without a sign-in wall, then asked for its own health.
Three commands, one per host
Pick one. The client, the server and the proxy are the same in all three; what changes is who runs the process and who holds the domain.
npm run deploy # Vercel
npm run build && npm start # your own server, PORT defaults to 3000
# Docker — VITE_* are baked into the client, so they are build args as well
docker build -t chatfuel-app \
--build-arg VITE_CHATFUEL_WORKSPACE_ID=<workspace> \
--build-arg VITE_SUPABASE_URL=<url> \
--build-arg VITE_SUPABASE_ANON_KEY=<key> \
--build-arg VITE_APP_NAME="Your app" .
docker run -p 3000:3000 --env-file .env chatfuel-appThe environment it reads
One .env, read in two places: VITE_ names are compiled into the browser bundle at build time, everything else is read by the server at runtime. On Vercel the deploy copies the values into the project itself — the file is never uploaded — and overwrites them rather than duplicating when you run it again.
| Name | When you need it |
|---|---|
| Always | |
| CHATFUEL_TOKEN | Every deployment, and the one the deploy refuses to start without: with no token the proxy has nothing to reach Chatfuel with. It is server-side only and has no VITE_ prefix, so it cannot be baked into the browser bundle even in principle. |
| With Accounts | |
| VITE_SUPABASE_URL | When your own customers sign up. Set it together with the anon key or leave both out — with exactly one of the two the proxy fails closed and answers every request ProxyAuthMisconfigured, so the deploy stops rather than shipping that. |
| VITE_SUPABASE_ANON_KEY | The other half of that pair. Both, or neither. |
| SUPABASE_SERVICE_ROLE_KEY | With Accounts, and not optional there: it is what registers the bot the server just created. Without it a sign-up ends on a workspace that is not ready, and nobody who signs up gets a bot. |
| CHATFUEL_WORKSPACE_ID | With Accounts. The Chatfuel workspace every account’s bot is created in — the one whose plan pays for them all, so its bot limit is the ceiling for the whole deployment. |
| With Admin and Publishing | |
| ADMIN_PASSWORD | When you want the operator panel. Sixteen characters at least or it refuses to run; unset, the admin routes are not mounted at all and /admin says there is no panel here. |
| PUBLISHING_SECRET | For scheduled posts, and only for those. Without it the app still composes and publishes on the spot — it just offers no schedule. |
| Optional | |
| VITE_CHATFUEL_WORKSPACE_ID | Without Accounts, to say which workspace the app opens on; leave it out and it opens on whichever one the account lists first. It is not a fence — every workspace the token owns is in the picker either way — and with Accounts on it is unused. |
| VITE_APP_NAME | Your name for the app, in the tab, the top bar and the sign-in screen. The wizard writes it at install and you can change it whenever. |
| VITE_APP_LOGO | Your mark. A file in public/ — the wizard copies in the one you gave it — or an absolute URL. |
| CHATFUEL_API_BASE | Only to point the proxy at a staging environment instead of the live API. |
| PUBLIC_URL | When scheduling is on and more than one name reaches the app. Without it the address the database posts back to is taken from whichever request turned scheduling on, which is fine on a single domain and not behind a load balancer. |
| PORT | On your own server, to move it off 3000. Vercel and Docker set their own. |
| VITE_BASE_PATH / BASE_PATH | When the app does not own the domain root. The first is the build-time prefix and the second the runtime one; they must match, and Vercel is root-only. |
Vercel, your own server, or Docker
All three run the same proxy source, so the auth gate, the bot fence and the error codes are identical between them. Nothing about the app is Vercel-specific except vercel.json and api/chatfuel.ts; deleting both leaves a working Node app.
| Name | Vercel | Your own server | Docker |
|---|---|---|---|
| Command | npm run deploy | npm run build && npm start | docker build, then docker run |
| Needs an account | A Vercel account | None | None |
| TLS and domain | Managed for you | Yours | Yours |
| Sub-path, as in /app/ | Root only | Yes | Yes |
| WebSocket lifetime | Capped by the function duration — 300 s on Hobby; the client reconnects | Unbounded | Unbounded |
| Where the environment lives | The Vercel project, and .env is never uploaded | Your process | --env-file, plus build args for the VITE_ names |
The rules that hold whichever host you pick
An unknown path must answer with index.html
The router is path-based, so /deals/board is a real address and a host that 404s it looks like a routing bug. The bundled Node server and vercel.json already do it; nginx needs one try_files line.
Build time and runtime have to agree
VITE_ names are compiled into the bundle, everything else is read by the server, and a deployment where the two disagree fails later at the gate rather than at the mismatch. /healthz says whether the auth gate is on, off or misconfigured — read that first.
Your own domain
TLS and the domain are managed for you on Vercel, and yours on a server of your own or in Docker. With Accounts, Supabase also has to be told the origin may receive auth redirects, or sign-in there fails.
npm run deploy is a convenience, not a dependency
It is a script inside your own project driving the Vercel CLI, and nothing in the app depends on it. Any CI that can run npm run build can deploy this app.
FAQs
What it costs to run, where it will not fit, and the one thing that makes a good deploy look broken.
Whatever you already pay for it. Vercel’s free tier runs the app; so does a Node process on a box you own, or a Docker container next to your other containers. None of the three report anything back to us.
See pricing