Skip to content

CLI ​

sctl is a short alias of stackctl: sctl run api seed is stackctl run api seed.

bash
stackctl                          # the terminal UI: pick services, run them
stackctl -s web -s api            # run these services, skipping the picker
stackctl --headless -s web        # no UI: stream logs to stdout
stackctl --mode test              # another mode: .env.test, its own session
stackctl stop                     # stop the mode's session
stackctl service restart -s api   # restart one service in the running session
stackctl run api seed             # one script, with api's environment
stackctl infra start              # start the infra, then exit

Every command works from anywhere in the repo: the workspace is the nearest directory, at or above the current one, that holds stackctl.config.ts. Without one, every command except init fails with No stackctl.config.ts in <dir> or any directory above it.

CommandWhat it does
startStart a session. The default command.
stopStop the mode's session.
resetStop it, then delete the mode's state.
serviceStart, stop or restart services in a running session.
statusThe session now, and the last commands that acted on it.
eventsPrint or follow the mode's event log.
show-configPrint the resolved config as JSON.
execRun a command with a service's environment.
runRun a script with a service's environment.
logsPrint or follow the session's log.
infraStart, stop, reset or destroy infra, or run Compose on it.
initSet the project up.

Global options ​

Accepted before or after any command.

OptionWhat it does
-m, --mode <name>The mode. Default: STACKCTL_MODE, else test in CI, else development.
--workspace-dir <dir>The directory holding the config, to run against a workspace from outside it. No walk-up.
-V, --versionPrint the version.
-h, --helpHelp for the command.

stackctl start ​

Starts a session. stackctl with no command is stackctl start, so stackctl -s web and stackctl start -s web are the same.

OptionWhat it does
-s, --service <name>A service to run, with its deps. Repeatable. Skips the picker.
--last-answersSkip the picker and reuse the selection it last saved.
--uiThe terminal UI. The default.
--headlessNo UI: stream the log to stdout. Needs -s or --last-answers.
-d, --detachFor CI: a headless session that outlives the command. Needs -s or --last-answers.
--wait-timeout <s>How long --detach waits for the session to be ready. Default 300.
--kill-othersQuit a session already running in this mode without asking.
--log-dir <dir>Where to write the session log. Default node_modules/.stackctl/modes/<mode>/logs/.

The first time a person starts a session in a terminal, stackctl offers to install the agent guidance. It asks once, and never asks an agent, a script or CI.

--headless ​

Runs in the foreground until the session is quit, printing every log line, date-stamped and labelled, with the infra's progress among them:

2026-11-10 16:00:11 [stackctl] [INFO] mode development (default)
▶ infra (development): db, mail
  mail  ready (0.4s)
2026-11-10 16:00:11 [infra:db] schema is up to date
  db    ready (0.5s)
✔ infra ready (0.5s)
2026-11-10 16:00:11 [api] started
2026-11-10 16:00:12 [api] listening on http://localhost:3001

A startup failure is printed, and the session shuts down and exits 1. Ctrl+C or a SIGTERM shuts it down gracefully.

--detach ​

bash
stackctl --mode test start --detach -s web

Starts a headless session with no tie to the terminal, waits until the infra and every service are healthy, prints one line and exits 0, leaving the session running:

test session ready: pid 4242, control API on 14012, log /repo/node_modules/.stackctl/modes/test/logs/1794164978752-4242-1.log
  • Progress goes to stderr, so $(…) captures that line alone.
  • On failure it stops the session, prints the error and the log's last 50 lines, and exits 1 with nothing left running.
  • End it with stackctl stop (same mode).
  • A session already running in the mode fails it, unless --kill-others is passed.
  • It is refused for a coding agent outside CI: a detached session would outlive the agent's task. See the agent playbook.

Taking over a running session ​

A workspace runs one session per mode. Sessions in different modes run side by side. Starting a second one in the same mode means quitting the first, with every process under it:

SituationWhat happens
--kill-others passedQuits the running session
Both sessions were started by a coding agentQuits it
A terminal is attachedAsks you to confirm
No terminal to ask inRefuses, exits 1, leaves it running

An agent can replace a session another agent started, never one you started. stackctl spots an agent by the environment variables agents set (CLAUDECODE, CURSOR_AGENT, GEMINI_CLI, CODEX_SANDBOX, AGENT, …). Set STACKCTL_STARTED_BY_AGENT=0 or =1 to override the detection.

A recorded session whose process is alive but doesn't answer its control API looks hung, and starting another in its mode is refused. --kill-others kills it, once it has confirmed the process really is that session.

stackctl stop ​

bash
stackctl stop                # the session of the resolved mode
stackctl --mode test stop

Asks the mode's session to quit and waits for its services to stop. A session that doesn't exit in time is force-killed with its process tree. Exits 0 when there was nothing to stop (No <mode> stackctl session is running.), and 1 if the session is still running.

Other modes' sessions and infra are never touched.

stop only signals a process it can confirm is the recorded session. When it can't, it says so and names the record: node_modules/.stackctl/modes/<mode>/running.json. Delete that file by hand if no session is running.

stackctl reset ​

bash
stackctl --mode development reset

Runs stop, then deletes the mode's state, node_modules/.stackctl/modes/<mode>/: its session record, saved selection, default logs and event log. --mode is required. Infra, other modes and logs written to a custom --log-dir are left alone. The reset writes its own lines to a new event log.

stackctl service ​

bash
stackctl service restart -s api              # restart api, wait until it is ready
stackctl service start -s worker -s web      # start them, adding any the session lacks
stackctl --mode test service stop -s worker

Drives application services in the mode's running session, through its control API. It never starts a session: with none running it exits 1.

SubcommandWhat it does
startStarts each service, adding one the session was started without.
stopStops each service. Services that wait on it keep running.
restartRestarts each service, starting a stopped one.

All take -s, --service <name>, repeatable. start and restart wait until the services are ready, up to --wait-timeout <s> (default 300), and exit 1 if one isn't. For infra services, use stackctl infra.

stackctl status ​

bash
stackctl status          # the mode's session, and what last acted on it
stackctl status --json   # the same, as JSON
development session: running since today 13:21:57 (pid 86046, control port 14037), started by a person
services  backend   running, not ready
          frontend  running, not ready
          worker    running, ready
infra     db        running
          mail      running
log       node_modules/.stackctl/modes/development/logs/1794424917144-86046-1.log
events    node_modules/.stackctl/modes/development/events.log

last commands
  today 13:21:57  session-start  backend, frontend, worker, headless
  today 13:21:57  infra-command  start every service                  ok, 646ms  by the session
  today 13:22:12  infra-command  restart db                           ok, 2.2s   by cli: infra restart -s db (a person)
  • The session is the mode's running one, checked as Finding the port does. Without one it says so.
  • The last commands are the event log's last 10 commands and lifecycle entries, each command's two lines as one row. See Events.
  • Read-only, and exits 0 with a session or without.

stackctl events ​

bash
stackctl events          # the mode's event log, one JSON object per line
stackctl events -n 20    # its last 20 lines
stackctl events -f       # print them, then follow
stackctl events --path   # print its path

The lines are the event log's, as they are in the file, for jq. It reads the file, a session running or not, and never asks the session anything.

stackctl show-config ​

bash
stackctl show-config
stackctl show-config --mode test
stackctl show-config | jq '.services | map_values({ port, publicOrigin })'
stackctl show-config | jq '.envFiles.workspace'

Prints the fully resolved config as JSON, with no session: callbacks have run and defaults are applied. It is the same object a session reports as config on GET /query/status.

FieldWhat it holds
servicesEach service's port, publicOrigin, urls, healthcheck, deps, command, relativeDir and absoluteDir.
infraPer provider: the compose file, the mode's projectName, and each service's enabled, port, urls and publicOrigin.
reverseProxyIts port, tls, publicAddress and routes.
envFilesThe .env files the mode looks for, in load order, at the workspace and service scopes.

Functions (logs.formatter, infra callbacks) have no JSON form and are left out.

stackctl exec and stackctl run ​

bash
stackctl exec [target] -- <command> [args...]
stackctl run [target] <script> [-- args...]

Run one command, or one package.json script or Makefile target, in a target's directory with its environment. The One-off commands guide covers targets.

OptionWhat it does
--log-dir <dir>Where to write the call's log file.
  • exec needs the --. Everything after it is the command.
  • run forwards what follows -- to the script. A script that doesn't exist is an error.
  • -s is not accepted. The target is the first argument.
  • The exit code is the command's.

stackctl logs ​

bash
stackctl logs          # print the session's log file
stackctl logs -f       # print it, then follow
stackctl logs --path   # print its path
OptionWhat it does
-f, --followStream new lines until Ctrl+C. The session keeps running.
--pathPrint the log file's path instead of its contents.

Read-only: it never starts or stops anything. It finds the log by asking the running session, else from the mode's session record, else the newest file in the mode's log directory.

stackctl infra ​

bash
stackctl infra start   [-s <name>...]
stackctl infra stop    [-s <name>...]
stackctl infra restart [-s <name>...]
stackctl infra reset   [-s <name>...]
stackctl infra destroy [-s <name>...]
stackctl infra docker-compose [args...]

Drives the infra the config declares, the same actions as a session's s, r and R keys. Every subcommand exits when it is done, and none disturbs a running session.

SubcommandWhat it does
startStarts the services, waits until each is ready and runs afterReady, then exits 0, or 1 if one failed. They keep running.
stopRemoves the containers. Data is kept.
restartstop, then start: the containers are recreated, picking up a changed compose file. Data is kept.
resetRemoves the services and their data, then starts them clean.
destroyRemoves the containers and their data, and starts nothing. With no -s, the whole project.
docker-compose [args...]Runs docker compose <args> on this project and mode, with your terminal.
  • -s, --service <name> picks the services, repeatable. With none, every enabled one: stackctl infra reset deletes every infra service's data.
  • start runs afterReady even for a service already running, so it applies new migrations to a database that is up.
  • reset here is not stackctl reset, which deletes the mode's session state and leaves infra alone.
▶ infra (development): db, redis
  redis  ready (0.4s)
[db] 2 migrations applied
  db     ready (3.1s)
✔ infra ready (3.1s)

--mode picks the mode's .env files and its Compose project. With the passthrough it must come before docker-compose, because everything after that word is Compose's:

bash
stackctl -m test infra docker-compose ps
stackctl infra docker-compose logs -f db
stackctl infra docker-compose exec db psql -U example

stackctl init ​

bash
stackctl init        # interactive
stackctl init -y     # accept the defaults

Creates stackctl.config.ts if there is none, adds it to the root tsconfig.json, and offers the agent guidance. Safe to run again.

FlagWhat it does
-y, --yesNo questions: the config, and the rules file and the skill.
--agents-mdAlso adds a stackctl section to AGENTS.md.
--workspace-dir <dir>Creates the config in that directory.

What the agent guidance is, on disk:

FileRead by
.claude/rules/stackctl.mdClaude Code, at the start of every session.
.claude/skills/stackctl/SKILL.mdAgents that load skills. Also under .agents/ and .cursor/ when those exist.
A section of AGENTS.mdAgents that read that file: Codex, Cursor, Copilot. Only with --agents-md.
  • The rules file and the skill are stackctl's own: a copy you edited is left alone, and init rewrites it.
  • The AGENTS.md section sits between two markers, <!-- BEGIN:stackctl --> and <!-- END:stackctl -->, in a new file if there is none. Everything outside them is yours.
  • Claude Code reads AGENTS.md only in a repo with no CLAUDE.md, which is why it gets a rules file of its own.
  • pnpm can link the skill itself. The package ships it in skills/, where pnpm approve finds it (pnpm 12.11 and later). Where pnpm's link is there, stackctl keeps no copy of the skill.

State ​

Everything stackctl keeps is under node_modules/.stackctl/:

node_modules/.stackctl/
  tls/                  the proxy's self-signed certificates, shared by every mode
  modes/<mode>/
    running.json        the session record: pid, control-API port, log file
    selection.json      the selection --last-answers reuses
    events.log          the event log: what acted on the stack (see Events)
    trusted.pem         the NODE_EXTRA_CA_CERTS bundle
    logs/               session and one-off command logs

Exit codes ​

CodeWhen
0The session was quit normally, or the command succeeded.
1Startup failed, a takeover was refused, or a command couldn't do its job.
the command'sexec and run exit with their command's code.
130, 143--detach interrupted by Ctrl+C or SIGTERM while waiting.

Released under the MIT License.