How Do I Run Flowise in a Sandbox on a Private URL?
Updated October 27, 2026 .md
Short answer: Run the flowiseai/flowise:3.1.3 Docker image in a small Sandboxer sandbox with a volume on /home/node/.flowise, bound to 127.0.0.1:3000, and host it with sbx host --localhost. Create the admin account first. Scripts call the prediction API with a Cookie __sbx_auth header plus a Flowise API key.
At a glance#
Everything here ran on one small sandbox (2 vCPU, 4 GB, 12 GB disk) on October 27, 2026, in eu-central. Single runs, not benchmarks; timings vary by host. No language model was used: the test flow is a JavaScript function, so no provider key was needed.
| Step | What we saw |
|---|---|
sbx new --type small |
Ready in 3.2 seconds |
Pull flowiseai/flowise:3.1.3 |
2 min 14 s, 5.7 GB on disk |
| Container start to "Flowise Server is listening" | About 9 seconds |
| Memory at idle | About 700 MiB |
| Hosted URL with no cookie | 401 from the Sandboxer proxy, including /api/v1/prediction/… |
| Prediction call from a laptop | 200, median 278 ms over 10 calls |
sbx stop --wait, then sbx resume |
2.2 s + 1.4 s, memoryRestored: true, same PID, first call 200 in 0.28 s |
sudo reboot (cold boot) |
Flowise back by itself, prediction 200 13.7 s after the reboot command |
Pick the image tag: 3.1.4 crashes with SQLite#
Flowise's Docker docs run flowiseai/flowise with data in /home/node/.flowise. We first tried the newest tag on October 27, 2026, 3.1.4. With the default SQLite database it restarted in a loop:
[ERROR]: TypeError: this.db.exec is not a function
at new SQLiteStore (/usr/local/lib/node_modules/flowise/node_modules/connect-sqlite3/lib/connect-sqlite3.js:56:17)
at initializeDBClientAndStore (…/enterprise/middleware/passport/SessionPersistance.js:96:23)That is upstream issue #6688: a newer connect-sqlite3 changed its API. 3.1.3 was built before that change and starts fine, so this page pins it. (Its /api/v1/version reports 3.1.2.) Check the issue before you move to a newer tag.
3.1.4 also runs as the node user. A fresh Docker volume is owned by root, so before it reached the SQLite crash it failed with EACCES: permission denied, mkdir '/home/node/.flowise/storage'. The chown line below fixes that. 3.1.3 runs as root and doesn't need it, but it does no harm.
Start Flowise#
sbx new prints the sandbox's subdomain. Ours was hasty-clear-moss, so the Flowise URL is https://hasty-clear-moss-3000.box.sandboxer.com.
sbx new --name flowise --type small
id=sb_z5r8i3bdpj # from sbx new
sbx exec $id --detach "docker pull flowiseai/flowise:3.1.3 > pull.log 2>&1"
sbx exec $id "tail -1 pull.log" # repeat until it prints the image nameThe pull takes a couple of minutes, which is longer than you want an attached sbx exec to wait, so it runs detached. Then save this as start.sh, copy it in with sbx scp start.sh $id:/home/user/start.sh, and run it with sbx exec $id "bash start.sh":
docker volume create flowise_data
docker run --rm --user root --entrypoint chown -v flowise_data:/home/node/.flowise \
flowiseai/flowise:3.1.3 -R node:node /home/node/.flowise
docker run -d --name flowise --restart unless-stopped \
-p 127.0.0.1:3000:3000 \
-e PORT=3000 \
-e DATABASE_PATH=/home/node/.flowise \
-e SECRETKEY_PATH=/home/node/.flowise \
-e LOG_PATH=/home/node/.flowise/logs \
-e BLOB_STORAGE_PATH=/home/node/.flowise/storage \
-e APP_URL=https://hasty-clear-moss-3000.box.sandboxer.com \
-e NUMBER_OF_PROXIES=1 \
-e DISABLE_FLOWISE_TELEMETRY=true \
-v flowise_data:/home/node/.flowise \
flowiseai/flowise:3.1.3
until curl -sf 127.0.0.1:3000/api/v1/ping; do sleep 0.5; donepongThe four path variables are the ones Flowise's Docker README lists for keeping flows, logs, credentials and uploads. -p 127.0.0.1:3000:3000 keeps the port off the sandbox's public address. NUMBER_OF_PROXIES=1 tells Flowise it sits behind one proxy, which is Sandboxer's.
Host it on a private URL#
Because the port is bound to localhost, host it with --localhost (Hosting):
sbx host $id 3000 --localhost --title flowise{"ok":true,"type":"port.hosted","sandboxId":"sb_z5r8i3bdpj","port":3000,"url":"https://hasty-clear-moss-3000.box.sandboxer.com?_token=…","access":"private","isProtected":true,"localhost":true}Open that full link in a browser once. It answers 302 and sets the __sbx_auth cookie, and the Flowise UI loads (<title>Flowise - Build AI Agents, Visually</title>). From a script, send the token as a cookie header instead:
B=https://hasty-clear-moss-3000.box.sandboxer.com
C="Cookie: __sbx_auth=<token>" # the _token value from sbx host
curl -s -o /dev/null -w "%{http_code}\n" $B/api/v1/ping # 401
curl -s -o /dev/null -w "%{http_code}\n" -X POST $B/api/v1/prediction/x # 401
curl -s -H "$C" $B/api/v1/ping # pong
curl -s -H "$C" $B/api/v1/chatflows # {"error":"Unauthorized Access"}The last line is Flowise's own login, not Sandboxer's. There are two gates, and the second one has gaps, covered next.
Flowise's own auth in 3.x#
FLOWISE_USERNAME and FLOWISE_PASSWORD are deprecated. Since 3.0.1, Flowise has email and password accounts, and the first account registered becomes the owner. On a fresh volume, whoever reaches the URL first can claim it, so do it while the port is private. The UI shows the setup form. The same call from a script:
curl -s -H "$C" -H 'Content-Type: application/json' \
-d '{"user":{"name":"Admin","email":"admin@example.com","credential":"<password>"}}' \
$B/api/v1/account/register{"user":{"id":"b7425843-…","name":"Admin","email":"admin@example.com","status":"active",…},"organization":{"id":"f88a244b-…","name":"Default Organization",…A second registration got 400 {"message":"You can only have one organization"}. Sign in with POST /api/v1/auth/login ({"email","password"}). Flowise sets token, refreshToken and connect.sid cookies, and its UI sends the header x-request-from: internal with them.
The gap is the prediction API. Flowise's server allows /api/v1/prediction/ without a login, so a chatflow with no API key answers anyone who can reach it. On a private Sandboxer URL that means anyone with the token. If you ever make the port public, assign each chatflow an API key first (chatflow-level auth).
Build a chatflow that needs no model#
We had no model key, so the test flow is an Agentflow with three nodes: Start, a Custom Function that counts words, and a Direct Reply. You can build the same thing on the canvas. We created it through the API with this script, make-flow.py, which writes a minimal flow definition. We didn't open it on the canvas afterwards.
import json
def node(id, name, label, x, inputs, vars=()):
# Flowise only fills {{ }} variables in inputs listed here with acceptVariable
params = [{"name": n, "acceptVariable": True} for n in vars]
return {"id": id, "type": "agentFlow", "position": {"x": x, "y": 0},
"data": {"id": id, "name": name, "label": label,
"inputParams": params, "inputs": inputs}}
js = """const q = $flow.input || '';
const words = q.trim().split(/\\s+/).filter(Boolean);
return JSON.stringify({ words: words.length, chars: q.length, upper: q.toUpperCase() });"""
nodes = [
node("startAgentflow_0", "startAgentflow", "Start", 0, {"startInputType": "chatInput"}),
node("customFunctionAgentflow_0", "customFunctionAgentflow", "Text stats", 300,
{"customFunctionJavascriptFunction": js}),
node("directReplyAgentflow_0", "directReplyAgentflow", "Reply", 600,
{"directReplyMessage": "Stats: {{ customFunctionAgentflow_0 }}"}, vars=["directReplyMessage"]),
]
edges = [
{"id": "e1", "type": "agentFlow", "source": "startAgentflow_0", "target": "customFunctionAgentflow_0"},
{"id": "e2", "type": "agentFlow", "source": "customFunctionAgentflow_0", "target": "directReplyAgentflow_0"},
]
flow = {"nodes": nodes, "edges": edges}
print(json.dumps({"name": "text-stats", "type": "AGENTFLOW", "flowData": json.dumps(flow)}))Two things we learned the hard way. Without inputParams the prediction failed with inputParams is not iterable. Without acceptVariable the reply came back as the literal text {{ customFunctionAgentflow_0 }}.
Create it, a Flowise API key, and attach the key. Sign in to a cookie jar first, then add __sbx_auth to the same jar so one -b carries both:
curl -s -c jar.txt -H "$C" -H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"<password>"}' $B/api/v1/auth/login
printf '#HttpOnly_hasty-clear-moss-3000.box.sandboxer.com\tFALSE\t/\tTRUE\t0\t__sbx_auth\t%s\n' '<token>' >> jar.txt
python3 make-flow.py > flow.json
H=(-b jar.txt -H 'x-request-from: internal' -H 'Content-Type: application/json')
curl -s "${H[@]}" -d @flow.json $B/api/v1/chatflows # {"id":"3752756b-…","type":"AGENTFLOW",…}
curl -s "${H[@]}" -d '{"keyName":"laptop","permissions":["chatflows:view"]}' $B/api/v1/apikey
curl -s "${H[@]}" -X PUT -d '{"apikeyid":"<key id>"}' $B/api/v1/chatflows/<flow id>Leaving out permissions returned 412 permissions must be an array of strings!.
To use a real model without a provider key, swap the Custom Function for an LLM node backed by Ollama on CPU, set up as in Run Ollama on CPU. We didn't test that here.
Call the chatflow from your laptop#
F=3752756b-688b-4890-8353-36ae2d6d1c82
curl -s -H "$C" -H "Authorization: Bearer <flowise api key>" \
-H 'Content-Type: application/json' \
-d '{"question":"Flowise runs fine in a sandbox"}' $B/api/v1/prediction/$F{"text":"Stats: {\n \"words\": 6,\n \"chars\": 30,\n \"upper\": \"FLOWISE RUNS FINE IN A SANDBOX\"\n}","question":"Flowise runs fine in a sandbox","chatId":"973f924f-…",…}The proxy strips __sbx_auth and passes Authorization through to Flowise. Every combination we tried:
| Sent | Answer |
|---|---|
| Nothing | 401, Sandboxer page |
Flowise API key, no __sbx_auth |
401, Sandboxer page |
__sbx_auth, no key or a wrong key |
500 {"message":"Error: predictionsServices.buildChatflow - Unauthorized"} from Flowise |
__sbx_auth + the right key |
200 |
Flowise reports a missing key as a 500, not a 401, so check the message. Ten calls with both headers took 255 to 323 ms each from the laptop.
With "streaming": true in the body, the answer comes back as text/event-stream through the hosted URL. You get nextAgentFlow events per node, a token event with the reply, then end with [DONE].
What survives stop, resume and reboot#
Everything Flowise keeps is in the volume: database.sqlite (accounts, flows, API keys, chat history), the generated encryption.key and JWT and session secrets, logs/ and storage/. It was 444 KB after this test.
sbx stop $id --wait # 2.2 s
sbx resume $id # 1.4 s
sbx info $id --json # "memoryRestored": trueSame container PID, and the first prediction after resume returned 200 in 0.28 seconds. Our Flowise login cookies from before the stop still worked.
For a cold boot we ran sudo reboot inside the sandbox. While Flowise was down, the hosted URL returned 502 (nothing listening yet). Docker started at boot, --restart unless-stopped brought the container back, and prediction answered 200 13.7 seconds after the reboot command. The flow, the API key and the admin login were all still there. The old login cookies still worked too, because the JWT secrets live on the volume, not in memory. For why the restart policy matters, see which way of starting a service survives resume.
FAQ#
Do FLOWISE_USERNAME and FLOWISE_PASSWORD still work?#
They're deprecated in Flowise 3.x. Flowise only uses them to let an older install move to the new account system. On a new install, register the first account, which becomes the owner, and keep the port private until you have.
Which size does Flowise need?#
small was enough here: about 700 MiB at idle, and the 5.7 GB image fits on its disk. Flows that call a local Ollama model need more memory, so use default for those.
Is the prediction API protected by Flowise's login?#
No. Flowise lets /api/v1/prediction/ through without a login. On a private URL the Sandboxer token still protects it. On a public URL, only a chatflow API key does.
Can a browser embed or chat widget call a private Flowise URL?#
Only from a browser that has already opened the token link and holds the cookie. For widgets on other sites, you'd need a public port plus chatflow API keys. We didn't test embedding.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.