StackPilot
Local microservice orchestration without Docker.
StackPilot starts your services in dependency order, streams logs in one
terminal, hot-reloads on file changes, and tracks actionable issues — all from
a single Stackfile.py.
pip install stackpilot
cd my-project
stackpilot sync
stackpilot run
Features
- One config file — declare services and external dependencies in
Stackfile.py - Auto-discovery —
stackpilot syncdetects FastAPI, Flask, Django, Celery, Express, NestJS, Postgres, Redis, MongoDB, RabbitMQ, and more - Apps vs infrastructure — only application services are started; Postgres/Redis are validated as external dependencies
- Dependency-aware startup — independent services start in parallel waves; dependents wait for healthy dependencies
- Health checks — HTTP, TCP, or process liveness before dependents launch
- Hot reload — file changes restart only the affected service
- Issue tracker — actionable crashes under
.stackpilot/issues/, not endless log files - Doctor — environment, runtime integrity, ports, env files, and config diagnostics in one command
- No Docker required for app processes (compose still works for databases)
Installation
Requires Python 3.10+.
pip install stackpilot
Verify:
stackpilot version
# or, if Scripts is not on PATH:
python -m stackpilot version
From source (contributors)
git clone https://github.com/subashsekar/stackpilot.git
cd stackpilot
python -m pip install -e ".[dev]"
pytest
Quick Start
stackpilot init # create Stackfile.py
# edit services, or:
stackpilot sync # auto-discover nested services
stackpilot run # start + stream logs (Ctrl+C to stop)
stackpilot stop # terminate a leftover session
stackpilot status # runtime table (PID / port / uptime)
You can also execute the Stackfile directly:
python Stackfile.py
Commands walk up from the current directory until they find Stackfile.py
(like Git finding .git). Runtime artifacts always land under that project
root in .stackpilot/.
Commands
The public CLI for v0.1.x is frozen until v0.2.0.
Ten commands. One job each.
| Command | Definition |
|---|---|
init |
Create a starter Stackfile.py |
sync |
Discover nested services and write Stackfile.py |
run |
Start services in dependency order and stream live logs |
stop |
Terminate leftover processes from .stackpilot/runtime.json |
graph |
Print a professional architecture dependency graph |
status |
Show runtime status (PID, port, uptime, health) |
ps |
List active StackPilot processes |
issues |
List actionable service issues from .stackpilot/issues/ |
doctor |
Diagnose environment, Stackfile, and service configuration |
version |
Print the installed StackPilot version |
stackpilot --help
stackpilot <command> --help
stackpilot init
Definition: Create a starter Stackfile.py in the current directory.
Use this when you are starting a new project and do not have a Stackfile yet.
It writes an empty template you can edit by hand, or overwrite later with
stackpilot sync.
stackpilot init
stackpilot init --force # overwrite an existing Stackfile.py
| Option | Definition |
|---|---|
--force |
Overwrite an existing Stackfile.py without prompting |
stackpilot sync
Definition: Walk nested directories, detect frameworks and infrastructure,
and generate (or overwrite) Stackfile.py.
Use this to bootstrap or refresh configuration from the project layout.
Application services become stack.service(...). Postgres/Redis become
stack.external_dependency(...) and are never given a start command.
stackpilot sync
stackpilot sync --force # overwrite Stackfile.py without prompting
| Option | Definition |
|---|---|
--force |
Overwrite an existing Stackfile.py without prompting |
Soft validation warnings (for example FastAPI without uvicorn) never abort sync.
stackpilot run
Definition: Validate external dependencies, start application services in dependency order, stream live logs, and hot-reload changed services until you press Ctrl+C.
stackpilot run # start the full stack
stackpilot run auth # start auth and its dependencies only
stackpilot run --force # clear a stale session, then start
| Argument / Option | Definition |
|---|---|
SERVICE (optional) |
Service name to start, plus everything it depends on |
--force |
Stop leftover processes / clear stale runtime.json, then start |
What it does:
- Detects a stale prior session (live PIDs or occupied StackPilot ports) and
asks you to run
stackpilot stop(or pass--force) - Checks required external dependencies (Postgres/Redis/MongoDB/RabbitMQ) with retry + timeout
- Starts apps in topological order and waits for health checks
- Streams
INFO/WARNING/ERRORlines to the terminal - Watches files and reloads affected services when enabled
- On Ctrl+C: disable reload → stop process trees → stop watchers → exit
130
Use status / ps for runtime tables and issues for persisted problems —
run itself only starts services and streams logs.
stackpilot stop
Definition: Terminate every service recorded in .stackpilot/runtime.json.
Use this when a previous stackpilot run did not shut down cleanly (closed
terminal, crash, etc.) and ports / processes are still held.
stackpilot stop
What it does:
- Reads
.stackpilot/runtime.json - Stops each recorded process tree (Windows Job Objects /
taskkill /T; Linux/macOS process groups) - Ignores already-dead PIDs and clears stale runtime entries
- Prints a short summary (
Stopping auth...→✓ N services stopped.)
If there is no runtime file (and no Stackfile project): No running StackPilot session.
Corrupted runtime files are cleared without a traceback. stop does not require a
Stackfile.py when a leftover .stackpilot/runtime.json is still present.
stackpilot graph
Definition: Print a professional architecture visualization of application services and external dependencies from the Stackfile.
Use this to understand startup order and depends_on relationships without
starting anything. Output includes the dependency tree, external services,
dependency depth, topological startup order, live status colors when a
session is active, ports, detected frameworks, and cycle highlighting.
ASCII fallback is used automatically on cp1252 / limited terminals.
stackpilot graph
Example output:
StackPilot Architecture
────────────────────────────────────────────
Services : 3
Running : 0
Stopped : 2
FastAPI : 2
Django : 0
Flask : 0
Node : 0
External : 1
🔴 gateway (:8000) [FastAPI]
└── 🔴 auth (:8001) [FastAPI]
└── 🔵 PostgreSQL (:5432)
────────────────────────────────────────────
Total Services : 2
Dependency Depth : 2
Circular Dependencies : None
Graph Generated Successfully
External nodes use infrastructure display names (for example PostgreSQL) and
are never started by StackPilot. Circular dependencies abort with a clear cycle
path.
stackpilot status
Definition: Show a runtime status report for the project: applications and external dependencies, including PID, port, uptime, and health when a session is active.
Reads .stackpilot/runtime.json written by a live stackpilot run session
(works from another terminal). Safe to run when nothing is running — services
simply show as stopped / inactive.
stackpilot status
Shows:
- Project name and whether a run session is active
- Applications — name, status, PID, port, uptime, health
- External Dependencies — reachability-oriented rows for Postgres/Redis, etc.
stackpilot ps
Definition: List active StackPilot processes in a concise table.
A shorter companion to status, focused on what is currently running
(name, PID, port, state).
stackpilot ps
stackpilot issues
Definition: List actionable service problems stored under
.stackpilot/issues/ (not full log files).
Default view is ACTIVE issues only. Use --fixed for recently fixed rows.
Pass a service name to filter to that service.
stackpilot issues # ACTIVE issues for all services
stackpilot issues --fixed # recently FIXED issues
stackpilot issues auth # every issue for service "auth"
stackpilot issues auth --fixed
| Argument / option | Definition |
|---|---|
SERVICE (optional) |
Limit output to one known service name |
--fixed |
Show recently fixed issues instead of ACTIVE ones |
An empty issues/ directory means the project is healthy.
stackpilot doctor
Definition: Diagnose the environment, Stackfile, dependency graph, ports, health checks, runtime integrity, and external dependencies in one report.
Use this when sync/run fails, imports break, ports conflict, or Postgres/Redis look misconfigured. Exit code is non-zero when errors are present.
stackpilot doctor
Checks include: Python / package import, Stackfile load, service paths and commands (missing executables), duplicate/free ports, dependency cycles, health-check / URL validity, env files, runtime.json integrity, orphan processes, project permissions, and external dependency reachability.
stackpilot version
Definition: Print the installed StackPilot package version and exit.
stackpilot version
python -m stackpilot version
Command cheat sheet
stackpilot init
stackpilot sync
stackpilot run
stackpilot run auth
stackpilot stop
stackpilot graph
stackpilot status
stackpilot ps
stackpilot issues
stackpilot issues --fixed
stackpilot issues auth
stackpilot doctor
stackpilot version
If no Stackfile is found:
No Stackfile.py found.
Create one:
stackpilot init
stackpilot sync
Then start services:
stackpilot run
First Project
1. Layout
my-project/
auth/
main.py # FastAPI app
gateway/
main.py
2. Discover
cd my-project
stackpilot sync
3. Generated Stackfile.py
from stackpilot import Stack, HttpHealthCheck
stack = Stack()
stack.external_dependency(
name="postgres",
type="postgresql",
host="127.0.0.1",
port=5432,
)
stack.service(
name="auth",
path="./auth",
command="python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000",
port=8000,
health_check=HttpHealthCheck(url="http://127.0.0.1:8000/health"),
depends_on=["postgres"], # external deps are validated, never started
)
stack.service(
name="gateway",
path="./gateway",
command="python -m uvicorn main:app --reload --host 0.0.0.0 --port 8001",
port=8001,
health_check=HttpHealthCheck(url="http://127.0.0.1:8001/health"),
depends_on=["auth"], # add application dependencies by hand when needed
)
stack.run()
StackPilot starts application services only. PostgreSQL, Redis, and other
infrastructure are declared as external_dependency entries: they appear in
graph / status / doctor, are TCP-validated before startup, and are
never spawned by Runner.
4. Run
stackpilot run
In another terminal:
stackpilot status
stackpilot ps
stackpilot issues
Ready-made samples live under examples/.
Also see examples/external-deps/ for a Postgres + Redis layout.
External Dependencies
StackPilot distinguishes application services from external dependencies.
| Application Service | External Dependency | |
|---|---|---|
| Declared with | stack.service(...) |
stack.external_dependency(...) |
| Has a process command | Yes | No |
| Started by Runner / ProcessManager | Yes | Never |
Validated before run |
Health check after start | TCP reachability with retry + timeout before any app starts |
| Shown in | Applications (status), graph | External Dependencies (status), graph as infrastructure labels |
Supported infrastructure
| Type | Default port | Validation |
|---|---|---|
PostgreSQL (postgresql / postgres) |
5432 |
TCP connect via the Health Engine |
Redis (redis) |
6379 |
TCP connect via the Health Engine |
MongoDB (mongodb / mongo) |
27017 |
TCP connect via the Health Engine |
RabbitMQ (rabbitmq / amqp) |
5672 |
TCP connect via the Health Engine |
stackpilot sync detects these directories / compose images and emits
stack.external_dependency(...) — never an executable stack.service(...).
How validation works
Before application startup, StackPilot probes each required external dependency
through the Health Engine (TCP preferred on the declared host/port).
Defaults (overridable per dependency):
| Setting | Default | Meaning |
|---|---|---|
retries |
5 |
Maximum probe attempts |
retry_delay |
0.5s |
Delay between attempts |
retry_backoff |
fixed |
fixed or exponential |
health-check timeout |
10s |
Hard deadline for the whole wait |
stack.external_dependency(
name="postgres",
type="postgresql",
host="127.0.0.1",
port=5432,
retries=5,
retry_delay=0.5,
retry_backoff="exponential", # or "fixed"
)
All reachable:
Checking external dependencies...
Checking PostgreSQL...
Attempt 1/5...
Connected.
✓ PostgreSQL (127.0.0.1:5432)
Checking Redis...
Attempt 1/5...
Connected.
✓ Redis (127.0.0.1:6379)
Starting application services...
One unavailable (retries exhausted):
Checking external dependencies...
Checking PostgreSQL...
Attempt 1/5...
Attempt 2/5...
Attempt 3/5...
Attempt 4/5...
Attempt 5/5...
✗ PostgreSQL is not reachable.
Problem: Dependency unavailable
Dependency: PostgreSQL
Host: 127.0.0.1
Port: 5432
Elapsed: 10.0s
Attempts: 5/5
Services depending on PostgreSQL:
- auth
- users
Suggested fix: Start PostgreSQL (or update host/port in Stackfile.py), then re-run `stackpilot run`. Verify with `stackpilot doctor`.
Startup aborted.
No application processes are started when validation fails.
Framework Support
stackpilot sync walks nested directories and asks the adapter registry
which framework matches. Soft validation warnings (for example FastAPI without
uvicorn) never abort sync.
| Framework | Detection signals | Generated command | Health |
|---|---|---|---|
| NestJS | package.json + @nestjs/core |
<pm> run start:dev |
HTTP /health when exposed, else TCP |
| Express | package.json + express |
<pm> run dev |
HTTP / |
| Django | manage.py + settings / WSGI / ASGI |
python manage.py runserver |
HTTP / |
| Celery | Celery() / worker modules |
celery -A <app> worker |
PROCESS |
| FastAPI | FastAPI() / common layouts |
python -m uvicorn <module>:<attr> --reload |
HTTP /health |
| Flask | Flask() / create_app() |
flask --app … run --host 0.0.0.0 --port N |
HTTP / |
| PostgreSQL | compose / postgresql.conf |
external_dependency (TCP 5432) — never started |
TCP |
| MongoDB | compose / mongod.conf / mongodb:// |
external_dependency (TCP 27017) — never started |
TCP |
| RabbitMQ | compose / amqp:// / rabbitmq:3 |
external_dependency (TCP 5672) — never started |
TCP |
| Redis | redis.conf / compose |
external_dependency (TCP 6379) — never started |
TCP |
| Generic | main.py / app.py / bare package.json |
python … or <pm> start |
PROCESS |
See examples/ for minimal projects per framework.
Package managers
Python: uv → Poetry → Pipenv → pip (with local .venv when present)
Node: bun → pnpm → yarn → npm (lockfile wins)
Port detection
.env/.env.*keys (PORT,APP_PORT, …)- Compose host port mappings
- Sequential defaults from
8000(Postgres5432, Redis6379, MongoDB27017, RabbitMQ5672)
When a preferred Flask port is already taken by another discovered service,
StackPilot assigns the next free coordination port and generates
flask run --host 0.0.0.0 --port {assigned} so the process actually listens
on the Stackfile port (not a hardcoded app.run(port=…)).
Custom adapters
Add one adapter file and register it in the adapter registry — nothing else.
Third-party code can also build a private AdapterRegistry.
Health Checks
Prefer typed models exported from stackpilot:
from stackpilot import ProcessHealthCheck, HttpHealthCheck, TcpHealthCheck
HttpHealthCheck(url="http://127.0.0.1:8000/health")
TcpHealthCheck(host="127.0.0.1", port=5432)
ProcessHealthCheck() # process must stay alive
Legacy dict configs ({"type": "http", "url": "..."}) remain supported.
Dependents wait until a service passes its health check (or the process stays alive for process health). Failures surface in the Issue Tracker.
External dependencies default to a TCP probe on their declared host/port
and are validated before any application service starts.
Issue Tracking
StackPilot does not persist normal service logs. Live INFO / WARNING /
ERROR lines still stream in the stackpilot run terminal — only actionable
problems are written under .stackpilot/issues/.
Each service owns one compact table file (for example auth.issue). Python
tracebacks are reduced to error message + project-relative path:line.
Duplicate ACTIVE rows are ignored.
ACTIVE
↓ (issue resolved / service healthy)
FIXED
↓ (1 hour)
Row removed
↓ (no rows left)
Delete <service>.issue
An empty issues/ folder means the project is healthy.
stackpilot issues # ACTIVE
stackpilot issues --fixed # recently fixed
stackpilot issues auth # one service
FAQ
Do I need Docker?
No. StackPilot only starts application processes. Postgres/Redis are external
dependencies — start them yourself (Docker, local install, managed DB, …);
StackPilot only checks that they are reachable before launching apps.
Where is the config file?
Always Stackfile.py — never stackpilot.py (that name would shadow the
package).
Is Stackfile.py trusted code?
Yes. Loading a Stackfile can execute arbitrary Python, and command= values
are spawned as your user. Review Stackfiles like you would a Makefile. See
SECURITY.md.
Can I run from a subdirectory?
Yes. Discovery walks parents like Git. Artifacts stay under the project root.
How do I stop everything?
Prefer Ctrl+C in the active stackpilot run terminal. To clean up after a
crash or closed terminal, run stackpilot stop — it reads runtime.json,
stops process trees, and clears stale entries. If stackpilot run reports an
existing session, run stackpilot stop or stackpilot run --force.
What happens during shutdown?
disable reload → stop processes → stop watchers → unbind → logger shutdown.
A shutdown summary lists each stopped service. Orphan children should not remain.
What about restart / hot reload?
While run is active, file watchers restart changed services (when
reload=True, or when Windows takes over uvicorn/Django/Flask-debug native
reload). Debounced callbacks are ignored once Ctrl+C begins shutdown. Limitations:
- Generator defaults do not set
reload=True; FastAPI often relies on uvicorn--reloadinstead. - On Windows, StackPilot strips uvicorn
--reload/ Django auto-reload / Flask--debugand owns restart soCTRL_C_EVENTcannot tear down the whole stack. - Reload restarts only the changed service (plus
restart_dependentswhen set).
Where did stackpilot logs go?
Live logs still stream in the run terminal. Persistence is the Issue Tracker
(.stackpilot/issues/) via stackpilot issues — not .stackpilot/logs/.
What if a service fails to spawn?
Missing executables, missing directories, permission errors, invalid commands,
and port conflicts print a short Problem / Affected service / Reason /
Suggested fix block without a raw traceback. Run stackpilot doctor for
deeper checks.
What if Postgres/Redis/MongoDB/RabbitMQ is down?
External dependency validation retries (default 5 attempts with delay/backoff)
until the configured timeout, then aborts before starting application
processes. The message lists host, port, elapsed time, attempts, dependents,
and the next action.
How do I check health without reading logs?
stackpilot status, stackpilot doctor, and stackpilot issues.
Known Limitations
- StackPilot starts application processes only. Databases and brokers must already be running (or reachable) as external dependencies.
- Generator defaults do not set
reload=True; many frameworks rely on their own reload flags (uvicorn --reload, etc.). - On Windows, StackPilot may strip native uvicorn/Django/Flask-debug reload and
own restarts so
CTRL_C_EVENTcannot tear down the whole stack. - Hot reload restarts the changed service (plus
restart_dependentswhen set), not the entire graph. stackpilot stopcleans recorded runtime PIDs; processes started outside StackPilot are not managed.Stackfile.pyis trusted code (see SECURITY.md).
Troubleshooting
| Symptom | What to try |
|---|---|
No Stackfile.py found |
stackpilot init or stackpilot sync from the project root |
| Sync finds nothing | Put each service in a nested directory (the project root itself is never a service) |
| Existing StackPilot session detected | stackpilot stop or stackpilot run --force |
| Corrupted runtime status | stackpilot stop (clears .stackpilot/runtime.json) then re-run |
| Port already in use | stackpilot stop, change port= / health URL, or free the port |
| Executable not found | Activate the project venv or fix command= — then stackpilot doctor |
| Permission denied | Check execute bits / antivirus locks on the service path |
| Invalid cwd / bad path | Fix path= so it exists under the project root |
| Dependency unavailable | Start Postgres/Redis/MongoDB/RabbitMQ (or fix host/port); wait for boot; re-run |
| Health endpoint missing | Confirm the route exists and matches health_check= |
| Health timeout | Check the run terminal, stackpilot issues <name>, then doctor |
| Service fails on start | stackpilot issues <name> and open the referenced FILE:LINE |
| Flask / Werkzeug banners look loud | Startup lines like * Serving Flask app are INFO, not ERROR |
| Import / CLI missing | pip install stackpilot then python -m stackpilot doctor |
| Wrong Python / venv | Activate the project venv, or use uv/Poetry/Pipenv so sync emits the right runner |
| Stackfile load / config error | stackpilot doctor — check syntax and that stack = Stack() exists |
| Ctrl+C leaves orphans | Upgrade to latest 0.1.x; report if process trees remain after shutdown summary |
stackpilot doctor
Contributing
See CONTRIBUTING.md for setup, tests, and the frozen CLI / architecture rules for v0.1.x.
python -m pip install -e ".[dev]"
pytest
Security
See SECURITY.md for the trusted-Stackfile threat model, supported versions, and how to report vulnerabilities.
License
MIT © StackPilot contributors
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stackpilot-0.1.0.tar.gz.
File metadata
- Download URL: stackpilot-0.1.0.tar.gz
- Upload date:
- Size: 334.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9089f93c60e707679c6f635a027e12b495eb6ab0962b6e6b496831a409d0c9ea
|
|
| MD5 |
eaea43fcc173347700b5e529c5b8173b
|
|
| BLAKE2b-256 |
9c2225e5046079a0370fc4c4c67fe46f477b93f59337fe0574f895ba009ac147
|
Provenance
The following attestation bundles were made for stackpilot-0.1.0.tar.gz:
Publisher:
publish.yml on subashsekar/stackpilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stackpilot-0.1.0.tar.gz -
Subject digest:
9089f93c60e707679c6f635a027e12b495eb6ab0962b6e6b496831a409d0c9ea - Sigstore transparency entry: 2358071007
- Sigstore integration time:
-
Permalink:
subashsekar/stackpilot@83af8bb527291eb076933490f2ee1a83afb199b7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/subashsekar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83af8bb527291eb076933490f2ee1a83afb199b7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file stackpilot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: stackpilot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 222.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b72b5600ac6c3f14a981e981890e49314befc3dfe010bea125495d1bfd5925ac
|
|
| MD5 |
d503a5d7b4cb8437e56acedcb392fe58
|
|
| BLAKE2b-256 |
07858f11ac5e5af9777ee1a3cfecd4b0ea348ab703cc540a77729d0aadd9924f
|
Provenance
The following attestation bundles were made for stackpilot-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on subashsekar/stackpilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stackpilot-0.1.0-py3-none-any.whl -
Subject digest:
b72b5600ac6c3f14a981e981890e49314befc3dfe010bea125495d1bfd5925ac - Sigstore transparency entry: 2358071065
- Sigstore integration time:
-
Permalink:
subashsekar/stackpilot@83af8bb527291eb076933490f2ee1a83afb199b7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/subashsekar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83af8bb527291eb076933490f2ee1a83afb199b7 -
Trigger Event:
push
-
Statement type: