pyinfra connector targeting LXD containers via the LXD HTTPS API (no SSH hop, no CLI subprocess, no websockets)
Project description
pyinfra-lxd-api-connector
A pyinfra connector that targets LXD containers via the LXD HTTPS API directly — no SSH hop, no paramiko, no lxc CLI subprocess, no websockets. One kept-alive HTTPS connection per host, exec via the container_exec_recording API extension, file transfers via the native files API.
Why?
The obvious way to drive pyinfra against LXD is to shell out to the lxc CLI for every command. That approach pays a per-command cost of ~6–7 fresh TCP+TLS connections (capabilities probe + events websocket + exec POST + 4× stdio websockets + operation poll) — measured at ~870 ms per command over Tailscale from a remote laptop.
Talking to the API directly with record-output: true mode collapses all of that to a single kept-alive HTTPS connection with zero websockets. Measured at ~150 ms per run_shell_command from the same vantage point — ~5–6× faster, and within the same order of magnitude as warm SSH-multiplex.
Tracks pyinfra issue #677. Per pyinfra's contributing guide, connectors live as separate packages rather than in the pyinfra core repo.
Performance
Per-call latency over Tailscale from a remote laptop (~27 ms RTT to the cluster), measured via smoke_test.py against a real container:
| Operation | Wall time |
|---|---|
connect() (cold TLS + capability probe + container check) |
~335 ms |
run_shell_command (warm, kept-alive) |
~130–260 ms |
put_file (small payload) |
~80 ms |
get_file (small payload) |
~30 ms |
For comparison, an lxc exec-based connector pays ~870 ms per run_shell_command from the same vantage point. From a node inside the cluster the difference doesn't matter; from a laptop driving deploys over a WAN it dominates wall time.
Install
uv tool install pyinfra --with pyinfra-lxd-api-connector
Usage
Prereq: an lxc remote configured locally:
lxc remote add mycluster https://your-cluster:8443 --token <token>
lxc list mycluster: # verify
The connector reads the standard LXD client config at ~/.config/lxc/:
config.yml— remote URLclient.crt+client.key— mTLS client identityservercerts/<remote>.crt— pinned server cert
Inventory:
hosts = [
"@lxd_api/mycluster:php01", # explicit remote
"@lxd_api/web1", # uses default-remote from lxc config
"@lxd_api/some-other-cluster:web1",
]
A bare @lxd_api/<container> resolves the remote via the default-remote field in ~/.config/lxc/config.yml — the same field lxc itself consults when called without a remote qualifier. Switch the default with lxc remote switch <name>. If no default is set, the connector raises an InventoryError pointing you at the qualified form.
Requirements
- LXD server with the
container_exec_recordingAPI extension (LXD 5.0+). - Local LXD client config at
~/.config/lxc/.lxc remote addsets all of this up.
Status
Alpha. In production use against a 32-container LXD cluster since 2026-04-28. Feedback / bug reports welcome.
Known limitations
- No interactive / PTY support — the connector raises
NotImplementedErrorif_get_pty=True. pyinfra never needs PTY for facts/operations, so this is fine in practice; if you need an interactive shell, uselxc shelldirectly. - Per-command stdout/stderr is buffered, not streamed —
record-outputmode means output arrives at the end of the command. For pyinfra's typical workload (facts and one-shot operations) this is invisible; for long-running commands you won't see live progress. - Run-time HTTP calls (exec, file transfer) don't retry — only the two
connect()GETs retry on transient errors. Mid-run network blips onrun_shell_command/put_file/get_filewill fail the operation. Per-call retry there is operation-dependent (e.g.POST /execis unsafe to blindly retry once it's reached the server). Tracked in #2.
AI assistance
Per the pyinfra AI usage policy, disclosing how this package was authored:
The initial draft of pyinfra_lxd_api_connector.py was generated by Claude (Anthropic) in collaboration with the maintainer (Christian Rishøj). Specifically:
- Christian identified the original problem — a silent SFTP-truncation bug in an earlier
lxc exec-based driver — and ran the empirical analysis showing that the LXDrecord-output: trueAPI path was the right fast alternative. - Claude drafted the connector module against pyinfra's
BaseConnectorinterface. - Christian reviewed every line, integrated and ran it against a 32-container production cluster, and iterated through several rounds of correctness, latency, and ergonomics fixes.
- All subsequent maintenance is human-driven.
The code in this repository is fully understood and reviewed by the maintainer; AI assistance is a drafting tool, not a substitute for human judgment.
License
MIT — see 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 pyinfra_lxd_api_connector-0.1.1.tar.gz.
File metadata
- Download URL: pyinfra_lxd_api_connector-0.1.1.tar.gz
- Upload date:
- Size: 15.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e432063e1d67e06951265172538cf3ef5ee125aa0efe0a5382dc804f70fd339
|
|
| MD5 |
c017f0c60b64e202925b575063bc7a4a
|
|
| BLAKE2b-256 |
daf1d8434d3f7c3c24bc9a1eec6fd8004cad4354b821f6d7d44648886f1f2771
|
Provenance
The following attestation bundles were made for pyinfra_lxd_api_connector-0.1.1.tar.gz:
Publisher:
release.yml on crishoj/pyinfra-lxd-api-connector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyinfra_lxd_api_connector-0.1.1.tar.gz -
Subject digest:
1e432063e1d67e06951265172538cf3ef5ee125aa0efe0a5382dc804f70fd339 - Sigstore transparency entry: 1397169502
- Sigstore integration time:
-
Permalink:
crishoj/pyinfra-lxd-api-connector@7ddf45960a89d498cb0e046c72e5273cf8c0f5bf -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/crishoj
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7ddf45960a89d498cb0e046c72e5273cf8c0f5bf -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyinfra_lxd_api_connector-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pyinfra_lxd_api_connector-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.4 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 |
c82a791f0c54b19bbb4b1d8d7e07e3fd2ca7f81d358f3f19627fa9280fec10d2
|
|
| MD5 |
2b207b6a5a3a18b578b59be0f3528996
|
|
| BLAKE2b-256 |
51fe91c17ea40f9ef79147b90cf10eab4dbe872e6e8178cf1e57d7e250e69170
|
Provenance
The following attestation bundles were made for pyinfra_lxd_api_connector-0.1.1-py3-none-any.whl:
Publisher:
release.yml on crishoj/pyinfra-lxd-api-connector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyinfra_lxd_api_connector-0.1.1-py3-none-any.whl -
Subject digest:
c82a791f0c54b19bbb4b1d8d7e07e3fd2ca7f81d358f3f19627fa9280fec10d2 - Sigstore transparency entry: 1397169511
- Sigstore integration time:
-
Permalink:
crishoj/pyinfra-lxd-api-connector@7ddf45960a89d498cb0e046c72e5273cf8c0f5bf -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/crishoj
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7ddf45960a89d498cb0e046c72e5273cf8c0f5bf -
Trigger Event:
push
-
Statement type: