Skip to main content

podsquire

podsquire is a small container init wrapper for workloads that need SPIFFE certificates, local mTLS proxy listeners, Vault-backed configuration, and simple subprocess supervision without baking that plumbing into the application.

It can run as PID 1 around your application, as a sidecar, or as a one-shot init container that writes certificates to a shared volume.

Features

  • SPIFFE/SPIRE certificate bootstrap — fetches an X.509 SVID from the SPIRE Workload API, writes tls.crt, tls.key, ca.crt, and optionally a combined key+cert PEM file, then renews before expiry.
  • Static certificate mode — use existing cert/key/CA files when SPIRE is not available, for example in local development.
  • mTLS proxy listeners — expose local plaintext HTTP or TCP listeners that connect to upstream services with the SPIFFE client certificate.
  • Subprocess supervision — launch an application command, optionally signal it on certificate or secret refresh, and restart it on failure with bounded retry limits.
  • Vault secret injection — authenticate to HashiCorp Vault with the pod's Kubernetes service account token and deliver KV secrets as environment variables or an atomically-written JSON file.
  • User-defined proxy presets — define your own environment-specific proxy shortcuts in configuration; no private service definitions are bundled in the package.
  • Shared platform service directories — load common proxy presets from a local directory/file or HTTP(S) directory/file at startup, with retries and warn-and-continue defaults.

All features are optional. Enable only the sections your container needs.

Installation

pip install podsquire

For local development from this repository:

pip install -e .

Quick start

Run with a YAML config:

podsquire --config /app/podsquire.yml

Fetch SPIFFE cert material and exit, useful as an init container:

podsquire --pull-certs-only /var/run/secrets/tls

That writes:

/var/run/secrets/tls/tls.crt
/var/run/secrets/tls/tls.key
/var/run/secrets/tls/ca.crt
/var/run/secrets/tls/tls.key+cert

Configuration overview

Copy config-example.yml and adjust it for your environment. Top-level sections are optional, but a long-running process should include at least one of:

  • spire or static
  • subprocess
  • proxies
  • enabled_proxy_presets
  • enabled_platform_services
  • vault_secrets

hooks is an optional event-action section layered on top of those features; it does not do anything on its own.

SPIRE certificate management

spire:
  # Optional. Defaults to SPIFFE_ENDPOINT_SOCKET, then /run/spire/sockets/agent.sock.
  # socket: unix:///run/spire/sockets/agent.sock

  cert_path: /tmp/podsquire/tls.crt
  key_path: /tmp/podsquire/tls.key
  ca_path: /tmp/podsquire/ca.crt
  combined_path: /tmp/podsquire/tls.key+cert

  renewal_interval: 60
  expiry_threshold: 3600
  retry_interval: 5

combined_path writes a single PEM file containing the private key followed by the certificate chain. Some clients, including PyMongo's tlsCertificateKeyFile, expect this shape.

Static cert mode

static:
  cert_path: /var/run/secrets/tls/tls.crt
  key_path:  /var/run/secrets/tls/tls.key
  ca_path:   /var/run/secrets/tls/ca.crt

Static mode does not renew certificates.

Subprocess supervision

subprocess:
  command: "python3 -m myapp --port 8080"
  path: /app
  process_group: true
  restart:
    enabled: true
    max_restarts: 5
    window_seconds: 300

subprocess controls the application process that podsquire supervises. Runtime events such as certificate renewal and Vault secret changes are configured under hooks.

By default, podsquire starts each command in a new process group. Hook signals, graceful shutdown, forced termination, and restart cleanup target that complete group, so pipelines, wrapper scripts, and background descendants are not missed or left orphaned. Set process_group: false to retain immediate-process-only signalling for commands that require it.

Runtime event hooks

Use hooks to tell the supervised subprocess about runtime updates:

hooks:
  certs_renewed:
    signal_subprocess: SIGHUP

  vault_secrets_changed:
    signal_subprocess: SIGUSR1

Supported hooks:

Hook Trigger Action Typical use
certs_renewed.signal_subprocess SPIFFE certificate files have been successfully renewed and written to disk Send the configured POSIX signal to the supervised subprocess Make the app reopen tls.crt, tls.key, ca.crt, or combined_path without restarting the container
vault_secrets_changed.signal_subprocess Vault refresh polling fetched secret values that differ from the previous fetch Send the configured POSIX signal to the supervised subprocess Make the app reload an atomically-written json_file or restart itself after env-backed secret changes

Signal names may be written as SIGHUP, HUP, string numbers such as "1", or integer signal numbers.

Both hooks target the subprocess configured under subprocess. If no subprocess is configured, or if the subprocess is not currently running, the signal action is a no-op. The hooks are independent, so an application can use different signals for certificate reloads and secret reloads.

vault_secrets_changed.signal_subprocess is only evaluated when vault_secrets.refresh_interval_minutes is greater than zero. The initial Vault fetch happens before the subprocess starts and does not send a reload signal. Later polls only signal on actual value changes, not on every poll.

Static cert mode does not renew certificates, so certs_renewed is never triggered for static: certs. --pull-certs-only fetches certs and exits, so no subprocess exists to signal.

Proxy listeners

proxies:
  - name: api
    mode: http        # http or tcp
    local_host: 127.0.0.1
    local_port: 18080
    remote_host: api.default.svc.cluster.local
    remote_port: 8443
    verify_remote: true

Modes:

Mode Behaviour Use for
http HTTP/1.1 reverse proxy; rewrites the Host header REST/HTTP APIs
tcp Raw byte tunnel MongoDB, gRPC, AMQP, custom TCP protocols

When verify_remote is true, the upstream server certificate is verified against the SPIFFE trust bundle written to ca_path. Hostname checking is disabled because SPIFFE SVIDs normally identify workloads with URI SANs rather than DNS SANs.

Shared platform service directories

Use platform_services when a platform team publishes common proxy listener definitions outside the application repo. The source can be a local directory, a local YAML catalogue, an HTTP(S) directory, or an HTTP(S) YAML catalogue. The path can be set in config or with PODSQUIRE_PLATFORM_SERVICES_PATH.

platform_services:
  path: /etc/podsquire/platform-services
  retries: 3
  retry_interval: 2
  timeout: 10
  fail_on_load_error: false
  fail_on_missing: false

enabled_platform_services:
  - vault
  - mongo

Directory mode fetches one YAML file per requested service name:

/etc/podsquire/platform-services/vault.yml
/etc/podsquire/platform-services/mongo.yaml
https://platform.example/podsquire/services/vault.yml

Each per-service file may contain a single proxy definition. The name field is optional in per-service files and defaults to the requested service name:

mode: http
local_host: 127.0.0.1
local_port: 8200
remote_host: vault.example.svc.cluster.local
remote_port: 8200
verify_remote: true

Catalogue mode is selected when the path ends in .yml or .yaml. The file can contain proxy_presets:, proxies:, or a direct mapping of service name to proxy definition:

proxy_presets:
  vault:
    mode: http
    local_host: 127.0.0.1
    local_port: 8200
    remote_host: vault.example.svc.cluster.local
    remote_port: 8200
    verify_remote: true

Failure behaviour is intentionally non-disruptive by default. If the common directory/catalogue cannot be loaded, podsquire logs a warning, logs the service names that could not be loaded, and continues startup with any remaining explicit proxies: entries. To make missing shared services fatal, set:

platform_services:
  fail_on_load_error: true  # fatal if the source cannot be loaded
  fail_on_missing: true     # fatal if a requested service name is absent

Explicit proxies: entries override shared platform services with the same name, so application-local emergency overrides remain possible.

Proxy presets

Podsquire intentionally does not bundle environment-specific service names or DNS records. If you want short names for your own platform services, define them in your config:

proxy_presets:
  vault:
    mode: http
    local_host: 127.0.0.1
    local_port: 8200
    remote_host: vault.example.svc.cluster.local
    remote_port: 8200
    verify_remote: true
  mongo:
    mode: tcp
    local_host: 127.0.0.1
    local_port: 27017
    remote_host: mongodb.example.svc.cluster.local
    remote_port: 27017
    verify_remote: true

enabled_proxy_presets:
  - vault
  - mongo

Explicit entries under proxies: take precedence when they use the same name as a preset.

Vault secret injection

vault_secrets:
  kv_path: apps/my-service/config
  url: http://127.0.0.1:8200
  role: my-service
  kv_mount_point: secret
  kv_version: 2
  output_mode: env
  refresh_interval_minutes: 0

Podsquire authenticates to Vault using the Kubernetes service account token at:

/var/run/secrets/kubernetes.io/serviceaccount/token

Supported output modes:

Mode Behaviour Best for
env Inject secrets into podsquire's environment before the subprocess starts/restarts Apps that read config from env at startup
json_file Write secrets atomically to a JSON file Apps that can reload config without restart
env_file Write shell-sourceable export KEY=value commands atomically CI wrappers that need to source Vault-backed variables

For shell env-file mode:

vault_secrets:
  kv_path: apps/my-service/config
  output_mode: env_file
  env_file_path: /tmp/env.sh

You can also use env-file mode directly from the CLI for compatibility with legacy CI wrappers:

podsquire --write-env-to-file /tmp/env.sh
source /tmp/env.sh

For JSON-file mode:

vault_secrets:
  kv_path: apps/my-service/config
  output_mode: json_file
  json_file_path: /var/run/secrets/podsquire/vault-secrets.json

hooks:
  vault_secrets_changed:
    signal_subprocess: SIGUSR1

Environment fallback variables:

YAML key Env var Default
kv_path VAULT_KV_PATH required if vault_secrets is enabled
url VAULT_URL http://127.0.0.1:8200
role VAULT_ROLE podsquire
kv_mount_point VAULT_KV_MOUNT_POINT unset
kv_version VAULT_KV_VERSION 1
json_file_path VAULT_JSON_FILE_PATH required for json_file mode
env_file_path VAULT_ENV_FILE_PATH required for env_file mode

Secret values are never logged. In env mode, all keys returned by Vault are placed into os.environ for the subprocess to inherit. In env_file mode, values are safely shell-quoted before being written. Keys containing ToBase64 are base64-encoded for compatibility with legacy env conventions.

Legacy reload-signal options

The older reload-signal fields are still functional for backwards compatibility, but the event-hook form is preferred and these fields will be deprecated in a future version.

Legacy certificate-renewal signal:

subprocess:
  command: "python3 -m myapp --port 8080"
  reload_signal: SIGHUP

Equivalent preferred form:

subprocess:
  command: "python3 -m myapp --port 8080"

hooks:
  certs_renewed:
    signal_subprocess: SIGHUP

Legacy Vault-secret-change signal:

vault_secrets:
  kv_path: apps/my-service/config
  output_mode: json_file
  json_file_path: /var/run/secrets/podsquire/vault-secrets.json
  reload_signal: SIGUSR1

Equivalent preferred form:

vault_secrets:
  kv_path: apps/my-service/config
  output_mode: json_file
  json_file_path: /var/run/secrets/podsquire/vault-secrets.json

hooks:
  vault_secrets_changed:
    signal_subprocess: SIGUSR1

If both the legacy field and the corresponding hook are configured with different signals, podsquire uses the hook value and logs a warning.

Deployment patterns

PID 1 wrapper

container start → podsquire → fetch certs → start app → supervise app
exec podsquire --config /app/podsquire.yml

Cert init container

podsquire --pull-certs-only /var/run/secrets/tls

Mount the same volume into the main container and point your application at the written PEM files.

Sidecar proxy

Run podsquire with spire/static and proxies, but without subprocess. The main application container connects to the local service address exposed by the sidecar, for example 127.0.0.1:27017 for a MongoDB TCP tunnel.

Demo connectivity app

python -m podsquire.connectivity_test is a tiny demo subprocess. Configure its checks with PODSQUIRE_CHECKS_JSON:

export PODSQUIRE_CHECKS_JSON='[{"name":"vault","url":"http://127.0.0.1:8200/v1/sys/health","ok_statuses":[200,429]}]'

It also demonstrates reloading the JSON secrets file on SIGUSR1.

Release and PyPI publishing

Publishing is automated from GitHub Actions when a SemVer git tag or GitHub release is created. The package version is derived from git tags with setuptools-scm; there is no committed version file to update.

Release checklist:

# 1. Make sure the release commit is clean and all checks pass:
python -m compileall podsquire
python -m pytest -q
python -m build
python -m twine check dist/*

# 2. Tag the release commit. The tag is the package version source of truth:
git tag -a v0.1.1 -m "Release v0.1.1"
git push origin main --tags

The publish workflow checks out the tag with full git history, derives the package version from the tag, and refuses to publish if the derived version does not exactly match the SemVer tag without its leading v.

The Publish to PyPI workflow uses PyPI Trusted Publishing/OIDC, so no PyPI API token is required in GitHub secrets. Configure the PyPI project publisher for:

  • owner/repository: tkdpython/podsquire
  • workflow file: .github/workflows/publish.yml
  • environment: pypi

The CI workflow builds and validates distributions on pushes, pull requests, and manual dispatches, but it does not publish.

Development checks

From the package directory:

python -m compileall podsquire
python -m pytest
python -m build
python -m twine check dist/*

Metadata

Release files for podsquire 0.1.4

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

Source distribution (sdist)

Source distribution for podsquire 0.1.4
File Size Uploaded
podsquire-0.1.4.tar.gz 40.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for podsquire 0.1.4
File Interpreter ABI Platform
podsquire-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 71.2 kB

Release files / podsquire-0.1.4.tar.gz

Download URL podsquire-0.1.4.tar.gz
Size 40.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3e9709c9bcd2b0bba9bc254c8fe7346d5c4031d92ac008eb98c6a5b30287e714
BLAKE2b-256 checksum
How to use checksums
910e6888e709990f90d5072318bf926e39e48502be71e200ed6e30ce9b425c9b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Aug 27, 2026.

Transparency log

Release files / podsquire-0.1.4-py3-none-any.whl

Download URL podsquire-0.1.4-py3-none-any.whl
Size 31.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
76401a76b67d17d4ee6f8dd01505200a6a7d7a7c53c4b36c50aa34b16cc6a52d
BLAKE2b-256 checksum
How to use checksums
6bafb2e010896492d3f4dcffa8a3669e793f03ee85d389b289495863aba8fdb9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Aug 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

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