# Deployment
The same Zebflow executable can run in three roles.
- **Standalone** runs the controller and office in one process.
- **Controller** handles platform control and coordination.
- **Office** runs projects and project owned services.
Start with standalone unless you need projects to run on separate workers.
`zebflow` (or `zeb`) starts standalone; `zebflow controller` and
`zebflow office` start the cluster roles (`master` and `worker` are deprecated
spellings). A controller or office needs its cluster variables and refuses to
start naming any that are missing:
| `ZEBFLOW_CLUSTER_MASTER_URL` | office | the controller's base URL |
| `ZEBFLOW_CLUSTER_JOIN_TOKEN` | office, first join only | `zfjoin2:<office_id>:<verify_key>:<secret>`, minted on the controller (`POST /api/platform/cluster/join-tokens`); stored afterwards at `<data-dir>/platform/office-join-token` |
| `ZEBFLOW_CLUSTER_ADVERTISE_URL` | both | the URL this node advertises (default: its own listen URL) |
| `ZEBFLOW_CLUSTER_NODE_ID` / `_NODE_LABEL` | both | stable id and label (default: the role name) |
How offices join, vouch and leave: help topic `guide/federated-offices`.
## Durable Data
Set `ZEBFLOW_PLATFORM_DATA_DIR` or mount `/var/lib/zebflow/data` in the container.
Without durable storage, projects disappear with the container or temporary
machine.
## Network
Use a reverse proxy for TLS and public routing. Send project execution routes to
the office that owns the project. Proxy WebSocket routes with upgrade support.
Long jobs should return a job identity and continue outside a short public HTTP
request.
## Health
The main application uses port `10610` by default. A separate health listener
can use `ZEBFLOW_HEALTH_PORT`, commonly `10611`. Use the separate listener for
liveness so a busy application route does not hide whether the process is
alive. Use readiness to decide whether the office should receive traffic.
## Before Production
- use a strong admin password
- keep the data root on durable storage
- back up projects and databases
- set CPU and memory requests based on measured work
- limit upload size at every proxy and at Zebflow
- use explicit outbound network rules
- set log retention
- test restore before relying on backup
## Offline maintenance
These run against a data directory with no server, for the day the server
will not start (stop it first — they refuse to run beside a live instance):
```
zebflow project config migrate <owner> <project> repo/zebflow.json → repo/zebflow.yaml
zebflow project lock migrate <owner> <project> pre-v1 zeb.lock format
zebflow project pipelines migrate <owner> <project> persisted pipeline ids → source-relative form
zebflow admin reset-password <owner> rotate to a fresh random password, printed once; the next login must choose a new one
zebflow admin break-glass [<owner>] re-enable local login on a joined office (recorded, reported to the controller later)
zebflow admin detach leave the controller; the office keeps everything it has
```
Kubernetes manifests for a cluster are generated by `zeb k8s cluster …`; the
Helm chart is under `charts/zebflow`.