Deploy with Compose¶
Install with the CLI¶
On a Docker host, install the command and start an instance from the CLI:
curl -fsSL https://raw.githubusercontent.com/quinguyen1502/superstep/develop/scripts/install.sh | sh
superstep install
The sections below are the Compose layout behind the full topology, and the
production checklist. Platform buttons for a hosted deploy stay in the
repository deploy docs.
Services¶
| Service | Image | Role |
|---|---|---|
postgres |
pgvector/pgvector:pg17 |
Workflows, runs, checkpoints, versions, vectors |
redis |
redis:7-alpine |
Run bus, job queue, rate-limit counters, live notifications |
api |
./backend |
FastAPI. Applies Alembic migrations and seeds on start-up |
worker |
./backend, python -m app.worker |
Runs queued jobs: API runs, schedules, recovered runs, webhook deliveries |
web |
./frontend |
nginx serving the Vite build and proxying /api and /ws |
jaeger |
jaegertracing/all-in-one |
Optional, with --profile tracing |
cp .env.example .env # set JWT_SECRET and POSTGRES_PASSWORD; never commit .env
docker compose up --build
python scripts/compose_smoke.py http://localhost:8080
The UI is at http://localhost:8080 (WEB_PORT). .env.example lists every
variable; the most used ones are in Configuration.
Production checklist¶
| Setting | Why |
|---|---|
STUDIO_ENV=production |
Refuses the placeholder JWT_SECRET, does not create the demo account, and deactivates it if it still has the demo password. |
JWT_SECRET |
Signs sessions. Long and random. |
STUDIO_SECRET_KEY |
Fernet key for stored credentials. Set it explicitly; losing it makes old credentials unreadable. |
STUDIO_ADMIN_EMAILS |
Bootstraps the first admin (the demo admin does not exist in production). |
STUDIO_PUBLIC_URL |
Public base URL for links in e-mails, approvals and webhooks. |
STUDIO_CORS_ORIGINS |
Only if a browser on another origin calls the API. Production allows none by default. |
STUDIO_TRUSTED_PROXIES |
CIDRs of the proxies in front, so client IPs for rate limits are read correctly. |
| TLS | Terminate HTTPS in front of web. The session cookie is Secure in production. |
An empty STUDIO_SECRET_KEY is generated once into the apidata volume
(/data/secret.key). That works, but back the volume up with the database.
Upgrade from v0.1.0¶
v0.1.0 was called LangGraph Studio, and its Compose project was
langgraph-studio. The project is now superstep, but the volumes keep their
v0.1.0 names (langgraph-studio_pgdata, _redisdata, _apidata), so the
database, queue and generated secret key carry over. Stop the old stack before
starting the new one. Otherwise Compose starts a second set of containers on
the same ports and volumes:
docker compose -p langgraph-studio down # keeps the volumes; never add -v
git pull && docker compose up -d --build
Also check what still uses the old name:
- Telemetry: the default
OTEL_SERVICE_NAMEis nowsuperstep. Set it tolanggraph-studioto keep existing dashboards. - E-mail: subjects start with
[Superstep]. Update mail filters. - Local runs (no Docker): the key and imports move to
~/.superstep/.~/.langgraph-studio/is still used while the new folder does not exist. - Files: templates exported and nodes copied from v0.1.0 still import and paste.
Database¶
GET /api/health reports database (postgresql or sqlite) and run_bus
(redis or memory). Leave DATABASE_URL empty in local development to use
SQLite backend/studio.db, and leave REDIS_URL empty to keep the run bus in
memory.
Start-up migrations take a lock so several processes do not migrate at once
(a Postgres advisory lock, or <db>.migrate.lock for SQLite). Schema changes
go through Alembic:
cd backend && .venv/bin/alembic revision --autogenerate -m "describe"
.venv/bin/alembic upgrade head
Workers and the queue¶
STUDIO_WORKER=1 (the default outside Compose) runs the worker inside the API
process. With REDIS_URL set, a separate process is python -m app.worker;
Compose sets the API to STUDIO_WORKER=0 and runs a worker service.
| Variable | Default | Meaning |
|---|---|---|
STUDIO_WORKER_CONCURRENCY |
2 | Jobs one worker runs at once |
STUDIO_JOB_VISIBILITY_TIMEOUT |
30 s | Lease on a job; the worker renews it while running |
STUDIO_JOB_REAP_EVERY |
15 s | How often expired leases are requeued |
STUDIO_RECOVER_ON_START |
1 | In-memory queue: requeue unfinished runs when the server starts |
STUDIO_RECOVER_MAX_ATTEMPTS |
3 | Restarts that may cut one run off before recovery gives up with RUN_INTERRUPTED |
On Redis, a job whose worker died is requeued when its lease expires, and
continues from the last checkpoint (run_recovered in the trace). Each time a
worker takes a run, runs.lease_gen increases; writes must carry that fencing
token, so a worker that lost the lease cannot commit and stops.
Runs across restarts¶
Superstep keeps its run checkpoints in its own database (SQLite by default,
Postgres with DATABASE_URL), not in LangGraph's in-memory saver, so a restart
or a crash loses no paused run:
| Run | After a restart |
|---|---|
| Paused at a Human node | Still waiting. Answer it in the studio, a chat channel or the API as before; it continues from its checkpoint. |
| Answered, but not yet continued | Continues with that answer. The answer is kept on the run until the run moves on, so an answer accepted just before the restart is not lost. |
| Queued or running API, schedule, chat or test run | Continues from its last finished node. Queue jobs save a progress checkpoint after every node; the node that was in flight runs again, finished calls are not repeated (effectively-once side effects). |
| Running in the editor | Ends with the browser connection; it is not stored until it finishes. Run it again. |
How unfinished queue runs come back depends on the queue:
- Redis (
REDIS_URL): the job stays in Redis; once its lease expires any worker takes it again. - In-memory queue (no
REDIS_URL, a single process): jobs held in memory are gone, so at start-up the server queues everyqueuedorrunningrun again as a recovered job, and every answer kept on a run that has not moved on yet. SetSTUDIO_RECOVER_ON_START=0only if several API processes share one database without Redis, since one of them would take over the runs of another.
A run that has been cut off by a restart STUDIO_RECOVER_MAX_ATTEMPTS times
(for example a node that keeps crashing the server) is not continued again: it
ends as error with code RUN_INTERRUPTED, keeping its events so far. Resuming
after a Human node or an Agent tool approval does not count.
Local models from Compose¶
Inside Compose, localhost is the container. api and worker set
extra_hosts: host.docker.internal:host-gateway, so point Ollama at
http://host.docker.internal:11434 and LM Studio at
http://host.docker.internal:1234/v1. On Linux run
OLLAMA_HOST=0.0.0.0 ollama serve and enable LM Studio's Serve on Local
Network; the firewall may need to accept the Docker bridge.
The Docker socket¶
Compose mounts /var/run/docker.sock into api and worker, for
Code mode and repo import. That socket is
root-equivalent on the Docker host. Keep both features off on a host you do
not trust. On Linux, if the container user (uid 10001) cannot open the socket,
set STUDIO_DOCKER_GID="$(stat -c %g /var/run/docker.sock)".