Operations

This guide covers day-to-day commands for local and remote environments.

Local Runtime

mkdir -p ~/sftpwarden-dev
cd ~/sftpwarden-dev
sftpwarden init dev --yes
sftpwarden deploy
sftpwarden doctor

Preview and apply user changes:

sftpwarden plan
sftpwarden refresh

Check project and runtime health:

sftpwarden health
sftpwarden health --json

sftpwarden init sets the created context as active. The recommended workflow is the Docker-style one: create a project directory, cd into it, initialize it, and run commands without repeating --context. Use sftpwarden context use dev to switch later, or pass --context dev/-c dev for one explicit command.

Applying Configuration Changes

Changes to sftpwarden.yaml are desired-state changes. They can come from sftpwarden config PATH VALUE or from a manual edit, and they are applied by the next deploy step:

sftpwarden validate
sftpwarden plan
sftpwarden deploy --dry-run
sftpwarden deploy --yes

For Kubernetes manifest projects, sftpwarden kube apply is the direct apply command. For Helm projects, use sftpwarden helm upgrade --install when you are not routing through sftpwarden deploy.

Deploy regenerates and applies the artifacts controlled by the config: Compose files, Kubernetes manifests, Helm values, runtime container settings, PVC/probe settings, provider bootstrap content for Kubernetes YAML/CSV projects, and forward provider schema migrations requested by provider.user_schema.

sftpwarden refresh reloads users already visible to the running runtime. It does not apply config changes. sftpwarden watch syncs editable user provider files for remote local-sync contexts and also does not sync sftpwarden.yaml.

Remote Deploy

Remote local-sync:

mkdir -p ~/sftpwarden-prod
cd ~/sftpwarden-prod
sftpwarden init prod --remote [email protected]:/opt/sftpwarden \
  --critical

sftpwarden deploy --dry-run
sftpwarden deploy --yes

Remote-only:

sftpwarden init archive --remote [email protected]:/opt/sftpwarden \
  --remote-only \
  --critical

sftpwarden refresh --dry-run

Remote setup checks verify SSH connectivity and docker compose version. Local deploys also check Docker Compose before applying the generated Compose file. Source checkouts build sftpwarden:local; Python package installations pull ghcr.io/kithuto/sftpwarden:<installed-version>.

Use sftpwarden context add when the project already exists on the remote host and you only need to register it locally:

sftpwarden context add prod [email protected]:/opt/sftpwarden --critical
sftpwarden context use prod

Context Cleanup

There are two cleanup paths:

  • If a project folder is deleted manually, the next context-aware SFTPWarden command prunes the stale local registry entry. For remote contexts this is local-only cleanup; SFTPWarden does not SSH to the remote host and does not remove remote Docker resources or remote files.

  • Remote-only contexts have no local project folder. If their remote project folder is deleted manually, the next real remote operation such as deploy, refresh, health, or backup removes only the stale local registry entry. If the remote server itself does not respond, SFTPWarden keeps the context and reports a controlled connectivity error with an SSH troubleshooting hint.

  • If you run sftpwarden context remove <name>, SFTPWarden treats that as an explicit cleanup request. It removes the local registry entry, deletes the project-owned local root when it is not shared with another context, stops the local Compose runtime when possible, and updates or uninstalls the watcher when needed.

Remote context removal keeps remote files by default in non-interactive mode:

sftpwarden context remove prod --yes

Interactive removal asks whether to delete remote runtime/project data. For CI or scripts, request that explicitly:

sftpwarden context remove prod --yes --delete-remote

Kubernetes Deploy

Compose remains the default deployment target. Pick Kubernetes during init when a project should be managed by kubectl or Helm:

sftpwarden init prod --deploy kube --yes
sftpwarden deploy --dry-run
sftpwarden kube apply

Helm mode stores the same target in sftpwarden.yaml, but delegates deploys to the official chart:

sftpwarden init prod --deploy helm --yes
sftpwarden helm values --write
sftpwarden helm lint
sftpwarden deploy --dry-run

When the CLI runs from a source checkout, Helm commands use the local charts/sftpwarden directory so chart changes can be tested before publishing. When SFTPWarden is installed from the Python package, Helm commands use the published OCI chart at oci://ghcr.io/kithuto/charts/sftpwarden with the same version as the installed CLI.

You can change the target later:

sftpwarden config deploy.target kubernetes
sftpwarden config kubernetes.mode helm
sftpwarden config kubernetes.namespace sftpwarden
sftpwarden config kubernetes.kube_context kind-sftpwarden

Kubernetes rendering is separate from applying. After a project exists, sftpwarden kube render and sftpwarden helm values do not require a live cluster. kube apply, kube status, kube logs, kube doctor, helm template, helm lint, helm upgrade, and helm uninstall require the matching external tool.

The default Kubernetes namespace is sftpwarden. During init, SFTPWarden checks the configured namespace with kubectl. If the namespace is missing, interactive init asks whether to create it; --yes accepts namespace creation by default. Pass --namespace <name> when you want a different existing or new namespace, or use --no-create-namespace to abort instead of creating a missing namespace. Use sftpwarden config kubernetes.namespace <name> later when a cluster policy requires a different namespace.

If you are preparing manifests or values before cluster access is available, run init with --skip-checks, then create or select the namespace before the first deploy.

File-backed providers use a provider PVC. YAML and CSV providers are declarative for Kubernetes projects: when SFTPWarden writes and applies manifests or Helm values from a local project, it renders the current local users.yaml or users.csv into the deployment and the init container copies that content into the provider PVC during the runtime rollout. That means the users in the cluster match the provider file that was deployed. Treat the rendered manifest or Helm values as operational deployment material because they include the YAML/CSV user entries that will be copied to the provider PVC.

sftpwarden refresh reloads users that are already visible inside the running runtime. It does not copy local YAML/CSV files into Kubernetes by itself, and the watcher is only for remote local-sync contexts. For YAML/CSV on Kubernetes, use sftpwarden deploy, sftpwarden kube apply, or sftpwarden helm upgrade --install after changing the local provider file. SQLite provider PVCs are initialized as a database file for single-pod lab use, but local SQLite files are not declaratively copied into Kubernetes.

The SFTP user data PVC defaults to 10Gi. Increase it through project config and deploy the generated manifests or values:

sftpwarden config kubernetes.data_storage_size 50Gi
sftpwarden plan
sftpwarden deploy --dry-run
sftpwarden deploy --yes

sftpwarden plan reports kubernetes.yml or values.yaml drift after the change. sftpwarden deploy updates the PVC request and restarts the runtime StatefulSet so the mounted volume is remounted. Your StorageClass must allow volume expansion; Kubernetes does not shrink existing PVCs.

Runtime healthcheck timing is also configurable. Compose projects use healthcheck.*; Kubernetes manifest and generated Helm projects use the kubernetes.*_probe.* settings:

sftpwarden config healthcheck.interval_seconds 45
sftpwarden config healthcheck.timeout_seconds 15
sftpwarden config kubernetes.startup_probe.failure_threshold 60
sftpwarden config kubernetes.liveness_probe.period_seconds 45
sftpwarden deploy --dry-run
sftpwarden deploy --yes

sftpwarden plan reports Compose, manifest, or values drift after these changes. sftpwarden deploy regenerates the deployment files and restarts or recreates the runtime as needed for the active deployment target.

Database providers should receive DSNs through a Kubernetes Secret. In Helm, set provider.dsnSecretName and reference the same environment variable from sftpwardenConfig; prefer creating the Secret outside the values file for production deployments.

SFTPWarden v1.3 supports one runtime pod per context. kubernetes.replicas and Helm runtime.replicas are reserved for future multi-node support and currently accept only 1.

The runtime entrypoint clamps the inherited open-file limit to 65536 by default before starting OpenSSH. This avoids container platforms that set an extremely high nofile limit causing internal-sftp chroot sessions to stall while closing file descriptors. Override SFTPWARDEN_NOFILE_LIMIT only when your platform has a specific reason to use a different value.

For serious Kubernetes environments, use PostgreSQL, MariaDB/MySQL, or MongoDB. The runtime reads those providers directly, so changes written to the database are available to the runtime sync loop and can also be forced with sftpwarden refresh. YAML/CSV fit GitOps-style deployments where deploy is the sync point, especially key-only or small reviewed environments. SQLite is acceptable only for single-pod lab deployments and should not be used for multi-writer production workloads.

Watcher

sftpwarden watch is only for remote local-sync contexts. It syncs YAML/CSV/SQLite user provider files to remote hosts. It does not sync sftpwarden.yaml.

sftpwarden watcher status
sftpwarden watcher install --dry-run
sftpwarden watcher install --watcher systemd --dry-run
sftpwarden watcher install --watcher docker --yes
sftpwarden watcher uninstall --yes

Watched files are derived from the context registry and provider configuration. Configuration, Compose, Kubernetes, and Helm changes require an explicit deploy.

Watcher installation defaults to auto. SFTPWarden detects the local host and chooses the first supported native scheduler:

  • Windows: Task Scheduler.

  • macOS: launchd.

  • Linux: systemd, OpenRC, runit, then supervisord.

Use a concrete backend when operations policy requires one:

sftpwarden watcher install --watcher systemd
sftpwarden watcher install --watcher openrc
sftpwarden watcher install --watcher runit
sftpwarden watcher install --watcher supervisord
sftpwarden watcher install --watcher launchd
sftpwarden watcher install --watcher windows-task

Watcher install writes the generated backend file and activates it by default. Use --dry-run to review the scheduler commands first, or --no-activate when you only want SFTPWarden to render the file. Linux native scheduler backends install service files under system locations, so their activation and uninstall commands use sudo and may ask for the host user’s sudo password.

Native watcher modes run sftpwarden watch on the host and use the host’s default SSH identity, agent, SSH config, known hosts, bastions, and ProxyJump. This is the recommended production shape when those SSH features matter. Windows native watcher sync uses OpenSSH scp for the single provider file; Linux and macOS use rsync.

If no native scheduler is detected, interactive installs ask whether to use the Docker watcher. With --yes, Docker fallback is accepted automatically. In non-interactive use, pass --watcher docker explicitly when that is the intended mode.

Docker watcher mode writes a Docker-specific context registry with Linux container paths, mounts local project folders read-only, and mounts only explicit dedicated SSH keys. The entrypoint copies those keys inside the container with private permissions before syncing. It does not mount ~/.ssh and does not require Docker socket access. Source checkouts build sftpwarden-watcher:local; Python package installations use ghcr.io/kithuto/sftpwarden-watcher:<installed-version> unless --image is set.

sftpwarden watcher uninstall deactivates the scheduler backend, removes the generated watcher file, and clears SFTPWarden’s watcher metadata. Installing a different watcher backend first deactivates the old backend, then writes and activates the new one. Removing or pruning the last remote local-sync context removes the watcher automatically; if a Docker watcher remains installed because other local-sync contexts still exist, SFTPWarden refreshes its generated context metadata.

Provider Transfer

Use provider transfer commands when you are moving users between storage backends, creating a portable snapshot, or copying users from one context to another.

Export users:

sftpwarden provider export --format json > users.json
sftpwarden provider export --output users.yaml

Import users into the active context:

sftpwarden provider import --input users.json --merge
sftpwarden provider import --input users.yaml --replace --dry-run

Copy users between contexts:

sftpwarden provider copy \
  --from-context dev \
  --to-context prod \
  --merge \
  --dry-run

--merge upserts source users and keeps destination-only users. --replace makes the destination exactly match the source. Provider transfer refreshes only when runtime-relevant user fields change; comment-only changes do not trigger a refresh. Kubernetes YAML/CSV destinations report that a deploy is required, because the provider PVC is synchronized during deploy rather than by refresh.

Backup and Restore

Create a project backup:

sftpwarden backup --output sftpwarden-prod.tar.gz --yes

Restore a backup:

sftpwarden restore sftpwarden-prod.tar.gz --yes

Backups include project config, Compose file, provider/users.json with the current users read from the provider, raw local provider files when available, host keys, and runtime state. SQL and MongoDB providers are captured through that JSON user snapshot when the CLI can reach the configured database. SFTP user data under data/ is excluded unless you pass --include-data.

Backups may contain secrets if DSNs or environment references are stored in sftpwarden.yaml. Store backup archives with the same care as infrastructure secrets.

User Schema Migration

Schema v1 keeps simple public_keys on each user. Schema v2 adds named keys and per-key lifecycle metadata. Inspect and migrate explicitly:

sftpwarden provider schema show
sftpwarden provider keys migrate --dry-run
sftpwarden provider schema migrate --to 2 --dry-run
sftpwarden provider schema migrate --to 2 --backup --yes

Advanced key commands such as disable, rename, rotate, expire, and import prompt before migrating a v1 provider to v2. Ordinary reads never rewrite provider data. Mutable migrations create a logical YAML backup by default unless --no-backup is used.

Changing provider.user_schema in sftpwarden.yaml does not migrate provider data immediately. The config command warns and asks before accepting a change that requires migration; manual edits are detected later. The next sftpwarden deploy, sftpwarden kube apply, or sftpwarden helm upgrade performs the forward migration before applying deployment changes, asks for confirmation unless --yes is used, and reports the backup path. If the config asks for an older schema than the provider data already uses, the command fails instead of downgrading.

Runtime State

Runtime state lives at /var/lib/sftpwarden/state.json inside the container and should be backed by the state/ volume.

Host keys live in /etc/sftpwarden/host_keys and should be backed by host_keys/ so server fingerprints do not change on restart.

Deleting User Data

By default, removing a user removes the provider entry and disables access after refresh. User files remain on disk:

sftpwarden user remove alice --yes

Use the explicit delete flag only when the data should be destroyed:

sftpwarden user remove alice --delete-files --yes

Troubleshooting

Runtime is not running:

docker compose ps
sftpwarden deploy
sftpwarden refresh

Remote checks fail:

ssh [email protected] true
ssh [email protected] 'docker compose version'

If docker compose version fails locally or remotely, install Docker Compose v2 before running sftpwarden deploy again. For remote-only contexts, also verify that the registered remote root still exists. If it was removed intentionally, recreate the remote project or register a new context.

Provider data changed but users did not update:

sftpwarden plan
sftpwarden refresh --dry-run
sftpwarden refresh

For Kubernetes YAML/CSV providers, use deploy/apply/upgrade instead because the local provider file must be copied into the provider PVC:

sftpwarden deploy --dry-run
sftpwarden deploy --yes

Healthcheck fails in Docker Compose:

sftpwarden health --json
docker compose exec sftpwarden sftpwarden runtime health --json

The generated Docker Compose file uses sftpwarden runtime health as its container healthcheck. Tune its timing with healthcheck.interval_seconds, healthcheck.timeout_seconds, healthcheck.retries, and healthcheck.start_period_seconds, then run sftpwarden deploy.