# How Do I Run Flowise in a Sandbox on a Private URL?

> 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.

Source: https://sandboxer.com/guides/run-flowise-in-a-sandbox. Updated 2026-10-27.

**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](https://github.com/FlowiseAI/Flowise/tree/main/docker) 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:

```text
[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](https://github.com/FlowiseAI/Flowise/issues/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`.

```bash
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 name
```

The 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"`:

```bash
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; done
```

```text
pong
```

The 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](/docs/hosting#apps-that-listen-on-localhost)):

```bash
sbx host $id 3000 --localhost --title flowise
```

```json
{"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:

```bash
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](https://docs.flowiseai.com/configuration/authorization/app-level), 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:

```bash
curl -s -H "$C" -H 'Content-Type: application/json' \
  -d '{"user":{"name":"Admin","email":"admin@example.com","credential":"<password>"}}' \
  $B/api/v1/account/register
```

```text
{"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](https://docs.flowiseai.com/configuration/authorization/chatflow-level)).

## 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.

```python
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:

```bash
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](/guides/run-ollama-on-cpu-in-a-cloud-sandbox). We didn't test that here.

## Call the chatflow from your laptop

```bash
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
```

```json
{"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.

```bash
sbx stop $id --wait     # 2.2 s
sbx resume $id          # 1.4 s
sbx info $id --json     # "memoryRestored": true
```

Same 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](/guides/run-a-service-that-comes-back-after-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

- [Give a sandbox port an HTTPS URL](/guides/give-a-sandbox-port-an-https-url)
- [Run n8n in a cloud sandbox](/guides/run-n8n-in-a-cloud-sandbox)
- [Run Ollama on CPU in a cloud sandbox](/guides/run-ollama-on-cpu-in-a-cloud-sandbox)
- [Hosting](/docs/hosting)
