Skip to main content

pydep

Deploy a Python/FastAPI application from its existing .venv as a persistent Windows Service, managed by NSSM. Standard-library-only CLI, Python 3.10 or later.

This package includes implementation and automated behavior tests. The tests use a service double, so actual SCM/NSSM integration, account permissions, and reboot behavior must still be verified on Windows using the procedure below. Passing the automated suite is not a claim that those integration checks ran.

1. Install NSSM

  1. Download NSSM from its official site. Use a build compatible with your Windows version, with command-line configuration and online log rotation support (2.24 or later).
  2. Extract the appropriate architecture's nssm.exe into a permanent, administrator-controlled location, for example C:\Tools\nssm\nssm.exe.
  3. Add C:\Tools\nssm to your Windows PATH through System Properties > Environment Variables, then open a new PowerShell window.
  4. Verify discovery:
Get-Command nssm.exe
nssm.exe version

Alternatively, supply --nssm 'C:\Tools\nssm\nssm.exe' to deploy/start/stop/remove. list queries Windows directly and logs reads files, so neither needs NSSM on PATH.

Keep the NSSM executable at the same location for the lifetime of its services. pydep does not download, redistribute, or automatically upgrade NSSM. See the official usage guide.

2. Install pydep

The package published on PyPI is named pydep-service, while the installed command remains pydep. The recommended installation uses uv's isolated tool environment:

uv tool install pydep-service
pydep --version
pydep --help

Run it once without installing it permanently:

uvx --from pydep-service pydep --help

Alternative installation methods are:

pipx install pydep-service
py -m pip install pydep-service

Install the CLI into a stable, isolated tool environment or Python installation, not an application venv you might delete. A deployed application continues to use its own .venv; pydep invokes that environment's Python directly and does not install FastAPI or Uvicorn for the application. Ensure the CLI installation's Scripts directory is on PATH. If pydep is not found after a pip installation, use py -m pydep with the same Python that installed it, or add that Python installation's Scripts directory to PATH. No runtime packages are required by pydep itself.

NSSM is a separate prerequisite: install it independently and make nssm.exe available on PATH (or pass --nssm where supported). Open PowerShell as Administrator for commands that change services: deploy, start, stop, and remove. The read-only list and logs commands do not require elevation.

Use the same Windows account each time. Elevating with a different account uses that account's %APPDATA% and therefore a different deployment registry.

3. Deploy D:\odin

For an existing application, keep its source and existing .venv. Ensure its Windows interpreter can import FastAPI and Uvicorn:

Set-Location D:\odin
.\.venv\Scripts\python.exe -m pip install fastapi uvicorn
pydep deploy --alias odin

If you are creating a fresh example, first create the venv with py -m venv .venv, create app\main.py, and put this in it:

from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

Example output, assuming the first ID is available:

[OK] Service reconciled: PyDep_A111
Deployment ID:  A111
Alias:          odin
Module:         app.main:app
Host:           0.0.0.0
Port:           8000
Path:           D:\odin
Status:         Running (Windows Service state)

Check the actual HTTP endpoint separately:

Invoke-RestMethod http://127.0.0.1:8000/health

Running is the observed Windows Service state, not an HTTP health guarantee. pydep waits for three seconds of running state after a start. Application startup failures, slow lifespan hooks, or later failures still require checking application logs and your health endpoint.

Commands

Command Behavior
pydep deploy Detect and deploy the current directory, or reconcile its existing registration
pydep deploy --alias odin Set or explicitly rename the alias
pydep deploy --module app.main:app Explicit import target, also stored for later deploys
pydep deploy --host 127.0.0.1 --port 8001 Set listening address and port
pydep deploy --restart Restart even if settings already match, to reload source changes
pydep list Show actual SCM state and registry operation phase
pydep start A111 or pydep start odin Start the registered service
pydep stop A111 or pydep stop odin Stop the registered service
pydep logs A111 or pydep logs odin Show the last 50 lines of each log
pydep logs odin --follow --lines 100 Tail both streams; Ctrl+C exits
pydep remove A111 or pydep remove odin Remove service and registration, archive logs
pydep remove odin --purge-logs Also delete this deployment's current and rotated logs

Representative command output:

> pydep list
ID     ALIAS                    STATUS       PORT   PHASE     PATH
A111   odin                     Running      8000   ready     D:\odin
A112   rental-api               Stopped      8001   ready     D:\rental-api

> pydep start odin
[OK] Started A111 (odin)

> pydep stop A111
[OK] Stopped A111 (odin)

> pydep logs odin
--- stdout: C:\Users\you\AppData\Roaming\pydep\logs\A111.out.log ---
INFO:     127.0.0.1:54321 - "GET /health HTTP/1.1" 200 OK
--- stderr: C:\Users\you\AppData\Roaming\pydep\logs\A111.err.log ---
INFO:     Application startup complete.

> pydep logs A111 --follow
--- stdout: C:\Users\you\AppData\Roaming\pydep\logs\A111.out.log ---
(no logs yet)
--- stderr: C:\Users\you\AppData\Roaming\pydep\logs\A111.err.log ---
INFO:     Application startup complete.
[stdout] INFO:     127.0.0.1:54322 - "GET /health HTTP/1.1" 200 OK

> pydep remove odin
[OK] Removed A111 (odin)

> pydep list
No deployments registered.

Outputs are illustrative. IDs depend on existing registrations and machine services. Commands exit with 0 on success, 1 on operational failure, 2 for invalid command syntax, and 130 on Ctrl+C.

Identity and idempotency

The canonical, resolved, case-insensitive project path is the deployment key. Repeated deployment from the same directory preserves its ID. Omitted alias/module/host/port options preserve stored values. Explicit options change desired configuration. Source changes alone are not fingerprinted: use --restart to reload changed application code or installed dependencies.

NSSM settings are compared with desired settings on each deployment. Drift is repaired. A configuration change restarts the service; an unchanged running service is left alone. A stopped or missing service is started or recreated. An alias-only change does not restart Python.

IDs start at A111, continue through A999, then B000 through F999. This makes the requested starting example explicit. The first free ID in that sequence is used, skipping machine services with occupied names. Removed IDs can be reused. Registry aliases are unique case-insensitively; ASCII letters, digits, and hyphens are allowed, up to 63 characters. Spaces and punctuation normalize to hyphens. ID-shaped aliases are rejected to prevent ambiguous resolution. Automatically generated alias collisions receive -2, -3, etc.; explicit collisions fail.

The registry is per Windows account; services are machine-wide. Aliases are unique within that account's registry. Another account's service name is skipped. Concurrent commands against one registry are serialized with a Windows file lock. Cross-account install races fail safely instead of taking over another service; they may require the recovery procedure below. Do not share a roaming registry across multiple machines or manage the same project under multiple accounts.

New deployments default to port 8000. There is no automatic port allocation. A port registered to another deployment in this account is rejected even if its service is stopped. Unregistered programs and other accounts can also occupy ports; inspect Uvicorn bind errors and choose --port 8001 when necessary.

FastAPI detection and execution

Discovery parses source with Python's AST; it does not import application modules. It supports app/main.py, main.py, and other main.py files up to four directory levels deep. It ignores .venv, venv, .git, __pycache__, hidden directories, tests, build output, node_modules, and directory symlinks.

Top-level instances created via from fastapi import FastAPI, renamed imports, or import fastapi as f are recognized. The instance can be named app, api, or another identifier. Ambiguous matches fail with the candidates rather than silently selecting the wrong app. Factories, dynamic creation, and src-layout import-path configuration are outside automatic detection. Use --module package.module:instance for an importable explicit target; factories are not supported.

The preflight imports FastAPI and Uvicorn using the project's Python. It does not import the selected app or verify its startup hooks. Uvicorn imports the target when the service runs.

The service uses the project's interpreter directly, with its working directory set to the project:

D:\odin\.venv\Scripts\python.exe -u -m uvicorn app.main:app --host 0.0.0.0 --port 8000

Venv activation is unnecessary. One Uvicorn process is used, without development reload. All subprocess calls use argument arrays and shell=False. NSSM's stored AppParameters value uses Windows argv quoting, not shell interpolation.

Storage and logs

pydep stores its own files under %APPDATA%\pydep:

  • deployments.json: versioned schema, ID, alias, absolute project and interpreter paths, service name, module, host, port, UTC timestamps, and operation phase.
  • deployments.lock: lock file; its presence does not mean a command is still running.
  • logs\A111.out.log and logs\A111.err.log: persistent stdout/stderr.
  • logs\archive\A111-<timestamp>\...: retained logs after removal, avoiding confusion when an ID is reused.

No application secrets or environment values are captured in the registry. Application logs can still contain sensitive information emitted by your code. Rotation is configured at approximately 10 MiB per active stream with NSSM online rotation. Rotated files are retained; there is no retention-age or total-disk-size cap. Apply your own retention policy. logs reads the current files and follows truncation or file replacement; recent output is limited to the last 1 MiB per stream. It does not merge streams chronologically or read historical archives.

remove stops and deletes only the owned Windows Service, then archives or purges deployment log files and removes its registry record. It never recursively removes a project, source directory, or .venv. Empty shared metadata directories and the lock file remain. Previously archived logs are not affected by --purge-logs for a newly reused ID.

Windows persistence and service identity

Windows Service Control Manager owns the NSSM service lifecycle. pydep configures automatic startup and NSSM application restart behavior, so closing PowerShell does not stop the API. Windows starts the service after reboot; NSSM supervises the Python process. See the NSSM command reference.

New services use NSSM's default LocalSystem account, which is highly privileged. Deploy only trusted applications, dependencies, and virtual environments. Protect the project, Python installation, NSSM executable, and metadata/log directories against modification by untrusted users. This small tool is intended for administrator-controlled hosts, not mutually untrusted tenants.

For a hardened host, after first deployment stop the service and select an appropriate dedicated account in services.msc under Log On. Grant that account read/execute access to the project, venv, base Python installation, and NSSM, plus write access to the log directory and any application data directories. Configure credentials through Windows, not pydep. Redeploy preserves an existing service's account. If a service was deleted and recreated, its account returns to NSSM's default and must be configured again.

Services do not inherit your interactive PowerShell environment, mapped network drives, or user login state. Use a local, continuously available project and interpreter. Manage application secrets through your existing service-compatible mechanism. pydep does not add firewall rules, TLS, a reverse proxy, or readiness dependencies. The default host binds all interfaces; choose 127.0.0.1 when placing IIS or another local proxy in front.

Failure recovery

Registry writes use a same-directory temporary file, flush/fsync, and atomic replacement. A desired-state record is saved before service mutation. SCM and JSON cannot be committed as one transaction, so failed or interrupted operations retain a visible phase:

  • pending: deployment/configuration/start was not fully completed. Fix the cause, return to the project, and rerun pydep deploy. The ID and desired alias/settings are retained. There is no automatic rollback to the old working configuration.
  • removing: removal was interrupted. Rerun pydep remove <id>. Do not redeploy until removal finishes.
  • ready: last deployment completed; current service status is queried separately.

The service description contains a pydep ownership marker derived from the metadata directory and deployment ID. Keep it unchanged. An ownership mismatch causes management to fail safely. This is an accidental-takeover guard, not a security boundary against administrators.

If execution stops in the small window between NSSM install and setting the description, the service may exist without a marker. Do not blindly remove it. First inspect services.msc, the pending JSON record, and:

nssm get PyDep_A111 Application
nssm get PyDep_A111 AppDirectory
nssm get PyDep_A111 Description

Only after confirming it is the service created for this deployment, stop it if running and remove it using nssm remove PyDep_A111 confirm. Keep the pending JSON record and rerun deploy. Never delete an unrelated service to free an ID.

For a corrupt registry, restore a known-good backup; pydep refuses to overwrite malformed content. Back up the registry before manual edits. If Windows reports a service is marked for deletion, close Services/Event Viewer handles and retry removal. If startup fails, inspect pydep logs <id> and Windows Event Viewer for missing imports, occupied ports, invalid settings, or account/path permissions.

Manual Windows acceptance test

Use a test machine and a fresh example project; replace the sample ID with the ID actually returned.

  1. Install NSSM and pydep, create the example under D:\odin, and deploy it. Verify /health returns {"status":"ok"}.
  2. Run pydep deploy three more times. Confirm one registry row, one Windows Service, and the same ID/alias. Inspect nssm get PyDep_A111 AppParameters and Start.
  3. Run pydep deploy --alias renamed; confirm the ID is unchanged, start renamed works, and the old alias is unknown. Rename back to odin.
  4. Stop by ID and confirm HTTP is unavailable; start by alias and confirm HTTP recovers. Repeat start/stop in the already requested state.
  5. Make HTTP requests and inspect logs odin, then logs A111 --follow. Stop and start the service; confirm existing output remains. Trigger nssm rotate PyDep_A111, issue more requests, and confirm follow continues into the new files.
  6. Close PowerShell. Check the endpoint from a new window. Reboot the test machine, then verify /health without running pydep start. Inspect the automatic startup setting and service account.
  7. Change service AppParameters manually to port 8999. Run pydep deploy; confirm the stored desired port is restored and the service restarts. Edit application code and run pydep deploy --restart to verify the new response.
  8. Create a second project. Confirm an explicit duplicate alias fails; deploy it with another alias and --port 8001. Confirm both appear once in list.
  9. Test missing venv/Python, --port 0, unknown ID/alias, and ambiguous FastAPI instances. Run a mutating command from a non-elevated terminal and verify the Administrator error. Test missing NSSM with an invalid --nssm path.
  10. In the disposable project, set an invalid import target with --module missing:app. Verify deployment fails or fails its HTTP check; inspect status/logs. Redeploy with the correct module and verify the same ID is recovered.
  11. Run pydep remove odin. Confirm Get-Service PyDep_A111 -ErrorAction SilentlyContinue returns nothing, list loses only that record, logs are archived, and all source and venv files still exist. Remove the second test deployment too.

Automated tests and layout

From the extracted package directory:

py -m pip install .
py -m unittest discover -s tests -v

The suite tests behavior with a service double and mocks the NSSM process boundary. It does not install or delete real services. The manual procedure above covers integration.

File Purpose
pyproject.toml Installable package and global console entry point
src/pydep/cli.py Commands, validation at CLI boundary, log display
src/pydep/core.py Discovery, identity, locked registry, reconciliation/removal
src/pydep/windows.py SCM queries, NSSM configuration and lifecycle
src/pydep/__init__.py Package version
src/pydep/__main__.py python -m pydep support
tests/test_pydep.py Automated behavior tests

Settings are centralized in NSSM.configure; registry schema version 1 keeps future extension explicit. No database, cloud, Docker, or large CLI framework is used.

Metadata

Release files for pydep-service 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydep-service 0.1.1
File Size Uploaded Provenance
pydep_service-0.1.1.tar.gz 29.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pydep-service 0.1.1
File Interpreter ABI Platform
pydep_service-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 49.1 kB

Release files / pydep_service-0.1.1.tar.gz

Download URL pydep_service-0.1.1.tar.gz
Size 29.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d3ed6789944e86973ed004289b75efee55eb9bb313eb9dfd42dab60813f7e03b
BLAKE2b-256 checksum
How to use checksums
36bd360e429c59ffc6296ad38522e1ebdd179d2491b9526c90f94700ae75b9b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pydep_service-0.1.1-py3-none-any.whl

Download URL pydep_service-0.1.1-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b2fe417299f8e9f795c8f5abca524a01e2ab02e97811d1c02fedf506f5d0fc9b
BLAKE2b-256 checksum
How to use checksums
c2a458908c40339275b737889e4a9349e81d536b21cfe4c3edcf53140c9f9820
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for pydep-service 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page