A deterministic deployment engine that turns a YAML file into git-sourced systemd user services behind a user-level NGINX, exposed through Cloudflare Tunnel.
Project description
sow 🌱
A deterministic, single-node Linux micro-platform control plane.
sow is a lightweight CLI and MCP (Model Context Protocol) server that turns a declarative YAML file into running systemd user services exposed through a managed NGINX reverse proxy and Cloudflare Tunnel.
It is designed for one machine, one configuration, and one execution loop. No schedulers, no container runtimes, no root daemons. Just your git-sourced Python/Go/Node apps running fast and lean on a VPS, Raspberry Pi, or homelab.
🎯 Why sow?
There is a massive gap between raw self-hosting primitives and heavy orchestration. Kubernetes and Nomad are overkill for a single node. Coolify and Dokku rely on Docker, which adds unnecessary overhead when your app is just a high-performance ASGI Python app or a compiled Go binary.
sow is a deployment engine, not an orchestrator. You define your stack in one file, and sow safely materializes the systemd units, renders the NGINX configs, checks out the exact git SHAs, and ensures everything runs as an unprivileged user.
Features
- Rootless by Default: Services run as
systemd --userunits. NGINX binds to a high local port. Nosudorequired after initial host setup. - Git-Driven Deployments: Every service is tied to a git repository and a specific commit SHA.
- Built-in Ingress & Exposure: Automatically configures a local NGINX proxy and routes public traffic via Cloudflare Tunnel.
- Agent-Ready (MCP): Ships with a native Model Context Protocol server over
stdio, allowing coding agents (like Claude Code) to inspect logs, update services, and debug infrastructure safely. - Fast & Safe: Written in Python, typed strictly, and managed via Astral's
uv. Applies are atomic—if a generated NGINX config is invalid or a service fails its startup health check, sow reverts the system to the last known-good state.
🚀 Getting Started
1. Installation
Install the sow CLI (requires uv):
curl -fsSL https://raw.githubusercontent.com/VictorBusque/sow/main/install.sh | sh
Note: sow runs entirely in user-space. Ensure git, nginx, and cloudflared are pre-installed.
2. Initialization
Bootstrap the platform on your host. This ensures systemd user services are lingering and NGINX/Cloudflare are properly configured to talk to each other:
sow init
3. Define Your Stack
Edit your declarative config at ~/.config/sow/sow.yaml:
version: 1
services:
api:
source:
git: https://github.com/me/my-fastapi.git
ref: main
build: pip install -r requirements.txt
command: python -m uvicorn main:app
args: ["--port", "${PORT}"]
environment:
LOG_LEVEL: info
DATABASE_URL: sqlite:///${DATA_DIR}/app.db
health:
http: { path: /healthz }
routes:
- host: api.example.com
paths:
/: { to: api }
exposure:
cloudflare:
credentials_file: ~/.cloudflared/app.json
hosts: [api.example.com]
4. Deploy
Reconcile the configuration and bring the system up:
sow up
To update the service to the latest commit on main later:
sow update api
🛠️ CLI Reference
sow's CLI is the authoritative interface for your infrastructure.
| Command | Description |
|---|---|
sow init |
Bootstraps host dependencies and the user-level daemon setup. |
sow validate |
Checks the YAML against the schema without modifying the system. |
sow apply |
Reconciles the YAML to system reality (clone, build, render, reload, health check). |
sow update <svc> [--ref <ref>] |
Fetches the latest commit on source.ref, writes the SHA, and applies. |
sow start <svc> |
Starts one systemd user service. |
sow stop <svc> |
Stops one systemd user service. |
sow restart <svc> |
Restarts one systemd user service. |
sow status |
Displays unified service health, route, and exposure status. |
sow logs <svc> [--lines N] |
Tails journald logs for a service (bounded tail; default 200). |
sow ps |
Lists running services and their systemd unit states. |
sow routes |
Lists configured host/path routes. |
sow exposure |
Lists hosts exposed through Cloudflare Tunnel. |
sow up |
Applies config and starts all services (the one-shot "make everything run"). |
sow down |
Stops all services; leaves config, generated files, clones, and data in place. |
📚 Documentation
| Doc | What it covers |
|---|---|
| docs/v1/prd.md | Product requirements: the four primitives, apply pipeline, ports, health, source/update semantics. |
| docs/v1/rfc.md | Technical spec: install/init, directory layout, execution & state model, security. |
| docs/v1/stack.md | Language, libraries, codebase layout, engineering conventions. |
| docs/v1/config-schema.md | Canonical sow.yaml field schema (types, defaults, validation rules). |
| docs/v1/cli-reference.md | Full CLI + MCP tool contracts (flags, exit codes, I/O schemas). |
| docs/v1/examples/full-stack.yaml | Exhaustive, realistic example config. |
| docs/v1/implementation-plan.md | v1 build roadmap: phased tasks, technical decisions, testing, risks. |
🤝 Contributing
We welcome contributions! sow is built on the Astral stack to guarantee high performance, rigorous type safety, and an excellent developer experience.
Local Development Setup
- Install
uv(our package manager):curl -LsSf https://astral.sh/uv/install.sh | sh - Clone & Sync:
git clone https://github.com/VictorBusque/sow.git
cd sow
uv sync
Engineering Standards
Before submitting a Pull Request, please ensure your code adheres to the project's strict quality gates:
- Formatting & Linting: Run
uvx ruff check .anduvx ruff format . - Type Checking: We use Astral's
ty(strict). Runuvx ty ..tyis still in beta, somypy --strictis the documented fallback — run whichever is current. No dynamic type drift is permitted in system-mutating logic. - Testing: Run the test suite with
uv run pytest. Because sow mutates system state, all unit tests testing the coreengine/must mocksubprocess.run(Git, systemctl, NGINX) and file system operations usingunittest.mockorpytestfixtures.
Architectural Rules
- XDG Compliance: Never hardcode paths to
~/. Always respect~/.config/sow/for user intent and~/.local/share/sow/for platform state. - Fail-Fast: Do not attempt partial recoveries in the engine loop. Catch
CalledProcessErrorand exit cleanly. - Atomic Writes: Any modifications to
state.jsonorsow.yamlmust be written to a.tmpfile and atomically replaced viaos.replace().
📄 License
sow is released under the MIT License.
Project details
Release history Release notifications | RSS feed
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 sow_cli-0.1.0.tar.gz.
File metadata
- Download URL: sow_cli-0.1.0.tar.gz
- Upload date:
- Size: 134.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17d3d985277b1dd547bfb1fef44e1fc31521aeac4f3472feb482b98a77375894
|
|
| MD5 |
af87800d831a7fbd5439640c2bba2a7d
|
|
| BLAKE2b-256 |
1f36114d74c3f9060ad26ed07523d4f0a38092e4dbc7b70b099bb6b62d61939c
|
Provenance
The following attestation bundles were made for sow_cli-0.1.0.tar.gz:
Publisher:
ci.yml on VictorBusque/sow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sow_cli-0.1.0.tar.gz -
Subject digest:
17d3d985277b1dd547bfb1fef44e1fc31521aeac4f3472feb482b98a77375894 - Sigstore transparency entry: 1870267749
- Sigstore integration time:
-
Permalink:
VictorBusque/sow@34a7fb5269c4fcbcf5faf0b004972a19b6130503 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/VictorBusque
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@34a7fb5269c4fcbcf5faf0b004972a19b6130503 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sow_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sow_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 60.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9dcc41c7a95473d6ec1d345096985dd2f51c6307f98d629670e949ebf2cf0937
|
|
| MD5 |
5b4a3356c64013da0c273d080c24342d
|
|
| BLAKE2b-256 |
25ae19c732dc90814c326aac1db529c2195f0ab650aa5cbe57d897c3fa4f53f5
|
Provenance
The following attestation bundles were made for sow_cli-0.1.0-py3-none-any.whl:
Publisher:
ci.yml on VictorBusque/sow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sow_cli-0.1.0-py3-none-any.whl -
Subject digest:
9dcc41c7a95473d6ec1d345096985dd2f51c6307f98d629670e949ebf2cf0937 - Sigstore transparency entry: 1870267852
- Sigstore integration time:
-
Permalink:
VictorBusque/sow@34a7fb5269c4fcbcf5faf0b004972a19b6130503 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/VictorBusque
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@34a7fb5269c4fcbcf5faf0b004972a19b6130503 -
Trigger Event:
push
-
Statement type: