vps-deployer
Deploy ordinary Linux services to a host with the operational properties of a small PaaS, without Docker or a resident agent.
vps-deployer packages a release locally, transfers it over OpenSSH, installs it
immutably, manages a hardened systemd service or timer, checks health, optionally
reconciles an nginx route, and rolls back failed activation. It targets operators
running roughly one to a dozen independent services on a normal Linux host.
It also supports command deployments: immutable releases exposed through a
validated /usr/local/bin launcher without creating a daemon.
It is deliberately:
- not configuration management;
- not a container runtime;
- not a build system;
- not a multi-host rollout orchestrator.
Applications arrive as prepared artifacts with an executable .deployer/run.sh.
Host packages, firewalls, TLS issuance, databases, DNS, kernel settings, and
unrelated users remain owned by host administration. The core product boundary
is a single-host deployment transaction.
flowchart LR
subgraph workstation["Operator workstation"]
app["Application repository<br/>code + .deployer contract"]
infra["Infrastructure repository<br/>hosts + deployment manifests"]
secrets["Operator environment<br/>secret values"]
deployer["vps-deployer"]
app --> deployer
infra --> deployer
secrets -->|"resolved locally; never printed"| deployer
end
deployer -->|"OpenSSH deployment transaction"| host["Existing Linux host"]
host --> releases["Immutable releases<br/>current + rollback target"]
host --> systemd["Hardened systemd<br/>service or timer"]
host --> nginx["Optional nginx route"]
host --> state["Persistent application data"]
When it fits
Use it for self-contained HTTP services, workers, bots, webhooks, and scheduled jobs that naturally run under systemd. Go/Rust binaries, packaged Java runtimes, prepared Node/Python artifacts, and small native daemons fit especially well.
Prefer another tool for container-image-first software, multi-service products, host provisioning, or coordinated fleet rollouts. Compared with Docker Compose, the application runtime stays Linux-native while the deployment lifecycle is managed. Compared with Ansible, vps-deployer owns application releases rather than general machine state.
Installation
Python 3.10 or newer and an OpenSSH client are required on the operator machine. The simplest install is an isolated tool environment:
pipx install vps-deployer
vps-deployer --version
pipx upgrade vps-deployer # later
To work from a checkout instead (for development):
git clone https://github.com/anfit/vps-deployer.git
cd vps-deployer
python -m venv .venv
.venv/Scripts/python -m pip install . # Linux/macOS: .venv/bin/python
The target host requirements are checked by host inspect and deployment
expectations; see the operations guide before onboarding a host.
Quick start
The bundled example validates offline, with no host required. It lives in the source repository and is not part of the pip package, so clone the repository first and point one variable at the clone:
export VPS_DEPLOYER_DIR=$PWD # PowerShell: $env:VPS_DEPLOYER_DIR = $PWD.Path
vps-deployer --repo ./examples validate
vps-deployer --repo ./examples secrets check # reports unset secret variables by name
plan, apply, and status connect to the host in examples/hosts/prod.yaml
(an SSH alias you must replace). To start your own infrastructure repository, run vps-deployer init DIR, or use
service-infra-template for a
fuller starting point with an operator guide. To prepare an application:
vps-deployer init app ./my-service # writes .deployer/run.sh, ignore.txt, expect.yaml, .gitattributes
vps-deployer doctor ./my-service # CRLF/shebang, credential-like files, contract syntax
Detailed documentation:
docs/build-properties.md— application-visible build and deployment provenance contract.docs/expectations.md— read-only application and infrastructure host contracts.docs/architecture.md— ownership model and remote layout.docs/manifest-reference.md— complete host and deployment schema.docs/operations.md— onboarding, deployment, status, rollback, removal, and troubleshooting.
Codex skills
Codex discovers two repository-scoped operational skills under .agents/skills:
operate-vps-deploymentsplans, applies, verifies, diagnoses, rolls back, and removes existing managed deployments.onboard-vps-serviceadapts a new or legacy service and adds its desired state toservice-infra.
Invoke them explicitly as $operate-vps-deployments or $onboard-vps-service, or describe a matching task while working anywhere in this repository.
Repository layout
Keep host identity separate from application deployment identity. Deployment files may be grouped recursively by application:
infra/
hosts/
prod.yaml
deployments/
invoicer/
prod.yaml
globals/ # optional; only genuinely shared non-secret values
artifacts/ # optional local release inputs
See examples/hosts/prod.yaml and
examples/deployments/invoicer/prod.yaml.
Ordinary configuration should be committed directly in a deployment. Use
from_global only for values intentionally shared by multiple deployments, and
use secrets.from_env only for secret material.
Multiple deployments may target the same host. An optional http_proxy block
manages a deployment's nginx site, TLS certificate references, loopback upstream,
configuration validation, and reload independently from other routes on that host.
The application repository owns .deployer/ and application provenance. The
infrastructure repository owns deployment identity, runtime configuration,
routing, storage, and secret references. Secret values remain in the operator
environment; persistent application data remains on the host.
Local artifact and include paths support explicit ${NAME} environment roots.
Unresolved variables and .. traversal are rejected. For example:
release:
source: ${PROJECTS_DIR}/my-service
include:
- source: ${SERVICE_INFRA_DIR}/configs/my-service/prod.yaml
target: config/production.yaml
Directory artifacts may contain a .deployer/ignore.txt file with gitignore-like
glob patterns. Matching developer-local files are excluded from both the release
hash and uploaded archive; use it to prevent local credentials and helper files
from entering managed releases. .vps-deployer-ignore remains a compatibility
fallback for applications that have not migrated.
Applications expose their managed runtime through .deployer/run.sh. This is the
default runtime.command, so ordinary infrastructure manifests need not repeat
an application-owned path. Named entrypoints such as .deployer/bootstrap.sh may
be selected explicitly for a distinct service lifecycle.
Every release contains an application-facing build.properties file. Existing
application/build properties are preserved and vps-deployer appends reserved
deployment.* provenance without modifying the checkout. status checks the
active deployment provenance against the desired release and commit; stale or
missing metadata makes the deployment unhealthy. After a successful activation,
the active release and its immediate predecessor are retained for rollback; older
release directories are pruned. Failed deployments do not trigger cleanup.
Commands
vps-deployer --version
vps-deployer init [DIR] # scaffold an infra repository
vps-deployer init app [DIR] # scaffold an application's .deployer/ contract
vps-deployer doctor [APP_DIR] # offline preflight of an application
vps-deployer validate
vps-deployer list [--format markdown] # inventory from the manifests (offline)
vps-deployer secrets check [DEPLOYMENT]
vps-deployer check DEPLOYMENT
vps-deployer host inspect HOST
vps-deployer host onboard HOST
vps-deployer plan DEPLOYMENT|--all [--exit-code]
vps-deployer apply DEPLOYMENT
vps-deployer status DEPLOYMENT|--all
vps-deployer logs DEPLOYMENT
vps-deployer rollback DEPLOYMENT
vps-deployer remove DEPLOYMENT
The infrastructure repository is chosen in this order: --repo, the current
directory when it contains hosts/ and deployments/, $VPS_DEPLOYER_REPO,
then $SERVICE_INFRA_DIR. Examples:
vps-deployer --repo ./examples validate
vps-deployer --repo ./examples plan invoicer-prod
remove stops and disables a deployment and removes its systemd unit and
environment file. It deliberately retains releases, writable storage, and service
users. Privileged deployments require --allow-privileged for both apply and
removal.
Hosts may set ssh.privileged_host, ssh.privileged_user, and
ssh.privileged_identity_file to route privileged operations through a separate
SSH identity. Use the packaged vps-deployer-ssh-gate (vps_deployer/data/; vps-deployer host bootstrap HOST prints or installs it) as that key's server-side forced
command so it cannot open an interactive root shell. The forced command must
declare the host's managed root and each storage path assigned to this key:
command="/usr/local/sbin/vps-deployer-ssh-gate --managed-root /srv/vps-deployer --storage /var/lib/example" ssh-ed25519 AAAA...
The client sends named capability requests; the gate constructs fixed executable arguments and rejects storage outside this policy. Update the forced-command installation before using a client containing this protocol change.
Systemd units use a separate validated gate capability rather than opaque file
writes. The gate accepts only the hardened non-root unit structure rooted in the
deployment's active release and allowlisted storage. A restricted-key host cannot
apply service.privileged: true; root services require a separately trusted sudo
or host administration path.
Secrets are accepted only through explicitly declared references
(secrets.<KEY>.from_env: NAME) and are never printed by planning or validation.
NAME is looked up in order in the process environment, then in a user-only
secrets file, then (optionally) the OS credential store, and the first hit wins.
The environment always comes first, so existing setups behave exactly as before.
secrets check lists unset variables by name (exit status 1 when any are
missing); apply refuses to start with unset secrets, and plan warns that they
are redacted. secrets list, secrets rotate and secrets unset manage stored values over time. See
operations.
Versioning and compatibility
Version 1.0 establishes the stable public contracts for deployment manifests,
the .deployer/ application interface, generated build.properties, CLI
commands, and the restricted SSH gate protocol. Compatible additions are made
within the 1.x series; incompatible contract changes require a new major
version and explicit migration documentation.
The client and server-side SSH gate are one security-sensitive protocol pair. When release notes identify a gate protocol change, install the matching gate before using the new client against a restricted-key host.
License
The complete vps-deployer codebase, including versions from project inception,
is licensed by Jan Chimiak under the Business Source License 1.1.
Production use is permitted except for paid hosted or embedded competitive
offerings. Each version converts to MPL 2.0 four years after its first public
distribution.
Metadata
Release files for vps-deployer 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| vps_deployer-1.4.0.tar.gz | 65.2 kB | With provenance | Details for vps_deployer-1.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| vps_deployer-1.4.0-py3-none-any.whl | Python 3 | none | any | Details for vps_deployer-1.4.0-py3-none-any.whl |
Total release size: 115.8 kB
Release files / vps_deployer-1.4.0.tar.gz
| Download URL | vps_deployer-1.4.0.tar.gz |
|---|---|
| Size | 65.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7bcad6de780cdf5da90bd140c6b38b4f63dba3c9e4ff38bd2036194a38e03cc0
|
|
BLAKE2b-256 checksum How to use checksums |
12e4b2a4116c65029d24c85f67282c131c5daee317ebf778d659f63bf32efcbe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / vps_deployer-1.4.0-py3-none-any.whl
| Download URL | vps_deployer-1.4.0-py3-none-any.whl |
|---|---|
| Size | 50.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
be869b06eb881b4fc90f773a4151e31bd6ceb661b1c5c19c439398a39da87a39
|
|
BLAKE2b-256 checksum How to use checksums |
5e910e35d236caef05c5a4a51d611a6112c13a2c9099d0d3513776f283ba37a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for vps-deployer 1.4.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vps_deployer-1.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA 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.