---
url: https://docs.fluxify.rest/deployments/kit.md
description: >-
  Run all of Fluxify in a single container using the fluxify-kit image — for
  prototypes and testing only, never production. Includes a batteries-included
  single command and a Docker Compose stack.
---

# Quick Run with the Kit Image

The **Kit** image (`fluxify-kit`) bundles everything Fluxify needs into a single
container: the admin API, the request worker, the dashboard, the AI gateway, and
a built-in proxy. It also ships its own database, cache, and event bus, so you
can start the whole thing with one command and nothing else installed.

One container, one port, one command — for **prototypes and testing only**.

> \[!WARNING]
> **Not for production**, not even on a single machine. Use the
> [Production Setup](./production) instead — it separates the control plane from replicated workers so you can
> scale request handling independently.

***

## What you get

| Item | Value |
| :--- | :--- |
| Containers to run | 1 (or 4, if you supply your own database, cache, and event bus) |
| Workers | A production worker serving every project by default, and a [development worker](#dev-worker) — both always run |
| Public port | `8080` |
| Best for | Prototypes and testing only. Not production. |
| Scaling | Vertical only (bigger machine) |

Traffic enters on port `8080` and is routed for you:

| URL | Goes to |
| :--- | :--- |
| `http://localhost:8080/_/admin/ui` | Dashboard (visual editor) |
| `http://localhost:8080/_/admin/api` | Admin REST API |
| `http://localhost:8080/_/admin/api/openapi/ui` | API documentation |
| `http://localhost:8080/` | Your published workflows & custom endpoints |
| `http://localhost:8080/_sandbox/<id>/…` | A [sandbox](/concepts/sandbox), served by the development worker |
| `http://localhost:8080/_/dev/…` | Your routes on the [development worker](/concepts/environments#calling-a-route-on-development), with the development token |

***

## Two ways to run it

Pick one. Both use the same image.

| | **Bundled** (recommended for trials) | **Bring your own services** |
| :--- | :--- | :--- |
| Command | `docker run` | `docker compose` |
| Database, cache, event bus | Included in the container | You supply them |
| Setup | One command, no config file | Copy and edit an environment file |
| Good for | Trying Fluxify out today | Keeping data in a database you manage |

The image decides automatically: if you tell it where to find a database, cache,
or event bus, it uses yours. If you don't, it starts its own.

***

## Option A — One command {#bundled}

Nothing to install, no configuration file:

```bash
docker run -d --name fluxify \
  -p 8080:8080 \
  -v fluxify_data:/data \
  -e SEED_USER_EMAIL=admin@example.com \
  -e SEED_USER_PASSWORD=ChangeThisPassword123! \
  ghcr.io/fluxify-rest/fluxify-kit:nightly
```

Then open `http://localhost:8080/_/admin/ui` and log in with the email and
password you just set.

> \[!NOTE]
> The kit uses `http://localhost:8080` as its public address by default. That
> one variable, `SERVER_URL`, is also Better Auth's base URL and the trusted
> origin. If you publish a different host port — for example `-p 9090:8080` —
> set `SERVER_URL` to the address you open. See [Changing the port](#bundled).

> \[!IMPORTANT]
> `SEED_USER_EMAIL` and `SEED_USER_PASSWORD` are **required** and have no
> defaults. They create the first administrator account, and nothing else can
> create it for you. A brand-new instance refuses to start without them rather
> than starting up with no way to log in. The password must be at least 8
> characters.

### About that `-v fluxify_data:/data`

Everything the kit stores lives in `/data`: your projects and routes, the event
history, and the security keys it generates the first time it starts.

**Don't skip the volume.** Without it, removing the container throws all of that
away.

> \[!WARNING]
> On first start the kit generates its own encryption and session keys and saves
> them in `/data`. **Back this up.** If you lose it, saved credentials — database
> passwords, integration keys — can no longer be read, even with the same
> projects restored.

### Changing the port

If `8080` is taken, map a different one on the left-hand side and tell Fluxify
its public address:

```bash
docker run -d --name fluxify \
  -p 9090:8080 \
  -v fluxify_data:/data \
  -e SERVER_URL=http://localhost:9090 \
  -e SEED_USER_EMAIL=admin@example.com \
  -e SEED_USER_PASSWORD=ChangeThisPassword123! \
  ghcr.io/fluxify-rest/fluxify-kit:nightly
```

***

## Option B — Bring your own database, cache, and event bus {#compose}

Use this when you want your data in a database you manage and back up yourself.

### Step 1 — Create your `.env`

Copy `docker/kit/env.example` to `docker/kit/.env` next to the compose file:

```bash
cp docker/kit/env.example docker/kit/.env
```

At minimum verify these values:

```env
#====================== ENVIRONMENT ======================
NODE_ENV=production
ENVIRONMENT=production

#====================== YOUR OWN SERVICES ======================
# Setting these three switches off the built-in copies.
PG_URL=postgres://postgres:postgres@postgres:5432/fluxify_alpha
REDIS_HOST=valkey
REDIS_PORT=6379
NATS_URL=nats://nats:4222
NATS_TOKEN=fluxify_nats_token

#====================== THE PROJECT THIS KIT SERVES ======================
# `*` serves every project and picks up new ones without a restart. Set a
# project id to pin the worker to one project.
WORKER_PROJECT_ID=*
INTEGRATION_TIMEOUT_POLICY_IN_SEC=450

#====================== SECURITY & KEYS ======================
MASTER_ENCRYPTION_KEY=<openssl rand -base64 32>
BETTER_AUTH_SECRET=<openssl rand -base64 32>

#====================== FIRST-RUN ADMIN ======================
SEED_USER_EMAIL=admin@example.com
SEED_USER_PASSWORD=ChangeThisPassword123!
SEED_USER_NAME=Admin User
```

> \[!WARNING]
> Back up `MASTER_ENCRYPTION_KEY`. If you lose or change it after storing data,
> every saved credential becomes unreadable.

> \[!NOTE]
> When you supply your own services you must also supply your own
> `MASTER_ENCRYPTION_KEY` and `BETTER_AUTH_SECRET`. The kit only generates those
> for you in bundled mode, where it has somewhere of its own to keep them.

### Generate your secret keys

Use this generator to create secure values for `MASTER_ENCRYPTION_KEY` and
`BETTER_AUTH_SECRET`, then paste them into your `.env`:

### Step 2 — Start the stack

```bash
docker compose -f docker/kit/docker-compose.yml up -d
```

This starts four containers: Fluxify plus the database, cache, and event bus.
Database setup runs automatically on first boot.

### Step 3 — Open the dashboard

```
http://localhost:8080/_/admin/ui
```

Log in with the seed admin credentials from your `.env`.

***

## Serving your projects {#worker}

This part applies to **both** options.

The request worker starts with the kit and serves **every** project by default
(`WORKER_PROJECT_ID=*`). Create a project in the dashboard, save a route, and it
is live at `http://localhost:8080/` — there is no project id to copy and no
restart.

> \[!TIP]
> The catch with `*`: two projects that define the same path — say both have a
> `/users` endpoint — collide, and only one of them answers. Fine while you have
> one project, which is the usual case for a trial. Give a project its own
> subdomain (**Project settings → General**) to keep equal paths apart.

To pin the worker to one project instead:

1. **Copy the project id** from the project's settings page.
2. **Set `WORKER_PROJECT_ID` to that id** — in `docker/kit/.env` for Option B, or
   as another `-e WORKER_PROJECT_ID=...` for Option A.
3. **Start the container again** with the new value.

Saving a route in the editor publishes it to the worker in place — no restart
and no redeploy. See [Request Lifecycle](/architecture/request-lifecycle) for
what happens on save.

To opt into experimental CPU-stall protection, set the project setting
`experimental.workerTimeouts.enabled` to `true`. The worker receives that change
immediately; no restart is required.

> \[!NOTE]
> One kit serves one project when pinned, or every project under `*` (the
> default). To serve several projects properly, move to the [Production
> Setup](./production), which runs a worker group per project.

### The development worker {#dev-worker}

The kit also starts a second worker for the **development**
[environment](/concepts/environments). It needs no setup: it serves every
project, so it runs from the very first start, before you have created one.

| | Production worker | Development worker |
| :--- | :--- | :--- |
| Serves | Every project by default; one project when `WORKER_PROJECT_ID` is pinned | Every project |
| Port (inside the container) | `5600` | `5602` |
| Health probe | `5601` | `5603` |
| Reached through port `8080` | Yes | Only `/_sandbox` and `/_/dev` |

Port `8080` sends `/_sandbox` and `/_/dev` to the development worker and
everything else to the production worker, so sandboxes, the playground,
`call_route` and `Run` work with no setting. A path that only starts with the
same letters, such as `/_devices`, still goes to the production worker. This
holds on a project's subdomain too. A project with a subdomain answers
`/_sandbox` and `/_/dev` only on that subdomain, so keep the subdomain's name
pointing at port `8080`.
The development worker does not count against your
[edition's](./editions#workers) worker limit.

The admin calls the development worker through the same port `8080`. A
deployment where the admin cannot reach that public address sets `PROXY_URL` to
the proxy's own address, for example `http://localhost:8080` for the kit's Caddy
— never to a single worker.

> \[!WARNING]
> Don't set `FLUXIFY_ENV` in the kit's `.env` or with `-e`. The kit sets it on
> the second worker only. Setting it for the whole container would turn your
> production worker into a development one.

***

## Connecting AI clients (MCP) {#mcp}

Fluxify has an MCP server, so AI apps and coding agents can work with your
projects. Point the client at:

```
http://localhost:8080/_/admin/mcp
```

The client signs in as a Fluxify user, and you approve it in the browser. It
gets the same access that user has. For sign-in to work, these addresses go to
Fluxify on the same port as everything else:

| URL | What it is |
| :--- | :--- |
| `/_/admin/mcp` | The MCP server |
| `/.well-known/oauth-protected-resource` | Tells the client where to sign in |
| `/.well-known/oauth-authorization-server` | The sign-in details |

Only paths starting with `/.well-known/oauth-` are taken for this. Any other
`/.well-known/...` path still reaches your own routes.

> \[!IMPORTANT]
> **Cloud AI apps need a public HTTPS address.** claude.ai and ChatGPT connect
> from their own servers, not from your computer, so they can't reach
> `localhost` or a private network. Your instance must be reachable from the
> internet over `https://`. Apps running on your own machine can use
> `http://localhost:8080`.

> \[!WARNING]
> **Use HTTPS on any address other than `localhost`.** On such an address,
> Fluxify tells MCP clients to sign in over `https://`, even when it is served
> over plain `http://`. Without HTTPS, sign-in fails.

**Check:** replace `<url>` with your address.

```bash
curl -i -X POST <url>/_/admin/mcp                                    # 401, with a WWW-Authenticate header
curl <url>/.well-known/oauth-protected-resource/_/admin/mcp          # JSON
curl <url>/.well-known/oauth-authorization-server/_/admin/api/auth   # JSON
```

The `401` is expected: it means the MCP server is up and asks the client to
sign in. The JSON lists your public address. If it shows another one, fix
`SERVER_URL` (and `TRUSTED_ORIGINS` if the portal is served on a different
origin).

***

## Plugins {#plugins}

A project can add a plugin by upload, npm, a link to a `.tgz`, or a folder.
Everything runs inside the one container, so there is nothing to mount: the
folder source reads `/data/plugins`, which is part of your `fluxify_data` volume.
Copy a built plugin (a subfolder with its `package.json`, `fluxify.json` and
`dist/`) there and pick it under **Plugins, From a folder**.

Links only work for hosts an instance admin trusts, which they set through the instance settings API (see [Adding a plugin](/plugins/installing#trusted-domains)). Every plugin waits for a creator to approve it.

***

## Upgrading

**Option A:**

```bash
docker pull ghcr.io/fluxify-rest/fluxify-kit:nightly
docker rm -f fluxify
# then run the same `docker run` command again — your /data volume is reused
```

**Option B:**

```bash
docker compose -f docker/kit/docker-compose.yml pull
docker compose -f docker/kit/docker-compose.yml up -d
```

Any required database updates run automatically at startup. If one fails, the
container stops and its log says `database migration failed`; the update is all
or nothing, so your data is left as it was. Back up before upgrading.

> \[!WARNING]
> In bundled mode, a major upgrade of the built-in database cannot be applied to
> data already on disk. Upgrades that change it are announced in the release
> notes, and the safe path is to export what you need before upgrading. This is
> one of the reasons the kit isn't meant for production — see
> [Production Setup](./production).

***

## Troubleshooting

**Container exits immediately, log says an admin email or password is required**
The first administrator account can only come from `SEED_USER_EMAIL` and
`SEED_USER_PASSWORD`. Set both (password 8+ characters) and start it again.

**Container exits immediately, other causes**
Check the logs: `docker logs fluxify` (Option A) or
`docker compose -f docker/kit/docker-compose.yml logs fluxify` (Option B). In
Option B the usual cause is a bad `PG_URL`, or a `NATS_TOKEN` that doesn't match
the one given to the event bus container.

**The container stops when one part of it fails**
That's deliberate. If any internal service dies, the whole container exits so
your restart policy brings it back, instead of leaving it running and quietly
broken. The log line naming the failed service is the last one printed.

**Can't log in after first run**
The seed admin is created only on the very first boot, against an empty
database. If you changed the values afterwards, they had no effect — reset the
password from the dashboard, or start over with a fresh volume. If the first
start failed before the login was created, restarting retries it.

**Everything was working, then a restart lost all my projects**
You most likely ran without `-v fluxify_data:/data`. Data lives in that volume;
without it, removing the container discards everything.

**Port 8080 already in use**
Map a different host port and set `SERVER_URL` to match. See
[Changing the port](#bundled).

**Requests to `/` return 502 Bad Gateway**
The request worker isn't running yet or has died. Check
`docker logs fluxify | grep worker`; the kit stops the container when any
internal service exits. See [Serving your projects](#worker).

**Requests to `/` return 404 Route not found**
The worker is running but doesn't have that route. Confirm the route is marked
active and, if you pinned `WORKER_PROJECT_ID`, that it belongs to that project;
then save it again to publish it.

**Routes save fine but never go live**
The event bus needs JetStream enabled. The bundled copy and the supplied compose
file both do this already; if you swapped in your own, start it with `-js`.

**Fluxify exits with "NATS server … is too old"**
Fluxify needs NATS 2.14 or newer; scheduled triggers rely on it. The bundled
copy is new enough. If `NATS_URL` points at your own NATS, upgrade it.
