Skip to main content

sync2pod

sync2pod manages persistent local DevSpace sync tasks. It writes a task definition, starts devspace sync in an independent background process group, records its runtime identity and log, and provides safe lifecycle, inspection, log, and cleanup commands.

This project is alpha software. Its configuration and command behavior may change.

Installation

Install from PyPI:

python3 -m pip install --upgrade sync2pod
hash -r
sync2pod --help

Run the same command later to upgrade to the latest published release. To confirm the installed package version, use:

sync2pod --version

Build and install a local wheel from source:

./scripts/build-package.sh
python3 -m pip install --upgrade dist/*.whl

Global command installation

Use pipx to install the latest PyPI release as an isolated global command on macOS:

brew install pipx
pipx ensurepath
pipx install sync2pod

Open a new terminal, then verify the command is on PATH:

sync2pod --help

Upgrade the global PyPI installation with:

pipx upgrade sync2pod

To replace the global command with a locally built wheel instead:

uv build --offline
pipx install --force dist/*.whl

Python 3.10 through 3.13 are supported. The wheel installs two equivalent console commands:

sync2pod --help
sync_local_to_pod --help

The sync_local_to_pod name preserves compatibility with existing scripts.

DevSpace task manager

The built-in devspace-upload template manages a local-to-pod DevSpace sync session. It starts the equivalent of devspace sync --upload-only --initial-sync mirrorLocal in the background, then records the task state and combined DevSpace output locally.

create requires DevSpace and checks that devspace sync is available. It then verifies Kubernetes access before writing the task, using the supplied --kubeconfig explicitly (with its context and namespace). Run the same minimal connectivity check yourself before creating a task:

kubectl \
  --kubeconfig ~/.kube/config \
  --context my-context \
  get namespace development \
  --request-timeout=10s

start <task_id> and restart <task_id> recheck that DevSpace is available locally. They always force DevSpace's initial sync mode to mirrorLocal, even if task.json was edited: the first pass is a full, local-authoritative mirror (uploading local differences and removing remote-only files under the project directory), followed by DevSpace incremental watching. Also verify that the label selector resolves to the intended container. The remote root directory (/) and double-slash paths are rejected to avoid mirroring into an unsafe target.

Create a task as shown below. create prints a fixed 12-character task ID; every later DevSpace command uses that ID, never the task name.

sync2pod create my-project \
  --local-path /Users/me/src/my-project \
  --remote-path /workspace/my-project \
  --kubeconfig ~/.kube/config \
  --kube-context my-context \
  --namespace development \
  --label-selector app=my-project \
  --container app \
  --exclude .git \
  --exclude .DS_Store

sync2pod start <task_id>
sync2pod list
sync2pod info <task_id>
sync2pod info

By default, create prints a human-readable task summary. With --json, it prints JSON similar to:

{
  "config_path": "/Users/me/.sync2pod/tasks/my-project/task.json",
  "id": "a1b2c3d4e5f6",
  "task_name": "my-project"
}

Keep the returned ID. Task names are only used when creating the definition; all lifecycle operations use the fixed task ID.

Output formats

All task commands except logs default to a human-readable display. Add --json for one machine-readable JSON value on standard output:

sync2pod list
sync2pod list --json
sync2pod info <task_id> --json
sync2pod start <task_id> --json

Diagnostics

Run doctor before creating or starting tasks to diagnose the local environment and every registered task:

sync2pod doctor
sync2pod doctor --json

Doctor checks that devspace and kubectl are available. For every registered task, it checks that the local path still exists, selects exactly one Running Pod using the task's kubeconfig, context, namespace, and label selector, and verifies that the configured container exists. It does not modify task files, Kubernetes resources, or DevSpace helper state. Doctor exits with status 1 when any check fails after reporting all available results.

logs is a streaming terminal command. Use --tail to select the initial line count, and --follow to print new DevSpace output as it arrives.

The generated command is equivalent to:

devspace sync \
  --kubeconfig ~/.kube/config \
  --kube-context my-context \
  --namespace development \
  --label-selector app=my-project \
  --container app \
  --path /Users/me/src/my-project:/workspace/my-project \
  --upload-only \
  --initial-sync mirrorLocal \
  --exclude .git \
  --exclude .DS_Store \
  --debug

Task files are stored under ~/.sync2pod/tasks/<name>/:

  • task.json: the DevSpace task template and its fixed task_id.
  • runtime.json: runner PID, process identity, command, lifecycle state, and timing information.
  • logs/current.log: current combined stdout and stderr of the background process.
  • logs/*.log: rotated logs from previous runs or rotations.

list prints the task ID, task name, and local upload path. info <task_id> prints its full configuration, configuration-file path, and state (running, stopped, or failed); info prints those details for every task. Runtime details can also include the PID, human-readable start/end times, exit code, and log path. runtime.json persists sync_state (initializing, syncing, synced, or failed), initial_sync (pending or completed), pending_changes, last_sync_at, last_change_at, and last_error. synced means DevSpace has reported completion for the current known changes; it does not imply that the background task has stopped watching for later local changes. A second start for a running task is refused. If the existing process identity cannot be verified, start, stop, restart, and delete fail safely instead of risking a second sync process or deleting an active task. Task names are immutable: delete an old task before creating a replacement with the same name.

Stop, restart, observe, or delete a task by ID:

sync2pod stop <task_id>
sync2pod restart <task_id>
sync2pod logs <task_id> --tail 50
sync2pod logs <task_id> --follow
sync2pod gc --dry-run
sync2pod gc
sync2pod delete <task_id>

stop, restart, and delete send SIGTERM to the isolated DevSpace process group, so helper processes stop with the parent. stop preserves the task definition and log so it can be started again. delete waits for a running task to stop before removing its task definition, runtime state, and log. Current logs rotate at 50 MiB; up to three archives are retained. gc removes logs for stopped or failed tasks seven days after their recorded end time while retaining task.json. It never removes logs of a running task.

Task lifecycle reference

Command Behavior
sync2pod create <name> ... Verifies DevSpace and the explicitly supplied kubeconfig, then persists a task definition and prints its task ID. It does not start synchronization.
sync2pod start <task_id> Rechecks DevSpace, performs a local-authoritative initial mirror, then starts incremental synchronization in the background.
sync2pod stop <task_id> Stops a running task but preserves its definition and log.
sync2pod list [--verbose] Prints registered task IDs, names, and local upload paths. --verbose also shows runtime state, sync state, remote path, and last sync time.
sync2pod info [task_id] Prints one task's details, or every task's details when the ID is omitted.
sync2pod restart <task_id> Rechecks DevSpace, stops the current process group, then runs a new local-authoritative initial mirror followed by incremental synchronization.
sync2pod logs <task_id> [--tail N] [--follow] Prints the current task log, optionally continuing as new lines arrive.
sync2pod gc [--dry-run] Lists or removes expired logs for stopped or failed tasks.
sync2pod doctor [--json] Read-only check of local dependencies and every registered task's local path, target Pod, and container.
sync2pod delete <task_id> Stops a running task and removes its local task definition, state, and log.

These commands do not create or delete Kubernetes API resources. start and restart run devspace sync, which may inject DevSpace's normal sync helper into the target container for the life of the local sync connection. logs only reads the local task log.

Legacy native engine quick start

The archive-based native engine remains available through the legacy commands and Python API for compatibility. Create a filesystem-backed project configuration, inspect it, and run a dry run before connecting to a pod:

sync2pod --init-config --local-path /path/to/project --project demo
$EDITOR ~/.sync2pod/demo/sync_config.json
sync2pod --list-projects
sync2pod --project demo --dry-run
sync2pod --project demo

Without --project, the project name defaults to the final component of --local-path during initialization.

Configuration

Each normal CLI project is stored as ~/.sync2pod/<project>/sync_config.json. Existing JSON project objects remain compatible with the standalone package. Unknown legacy fields are ignored.

A standard kubectl configuration looks like this:

{
  "cluster": "development",
  "namespace": "workspace",
  "pod_label": "app=my-service",
  "remote_parent_path": "/workspace",
  "local_path": "/Users/me/src/my-service",
  "container": "app",
  "kubectl_command": ["kubectl", "--context", "development"],
  "follow_sync": [],
  "exclude_paths": [".git", "node_modules", "*.log"],
  "max_workers": 10,
  "debounce_seconds": 1.0,
  "no_watch": false,
  "skip_verify": false,
  "prune": true,
  "upload_chunk_count": 6,
  "target_chunk_size_mb": 64
}

Wrapper commands are represented as an argument array, not a shell string. {cluster} is replaced with the configured cluster value:

{
  "cluster": "development",
  "kubectl_command": ["cluster-wrapper", "--target={cluster}", "kubectl"]
}

The complete schema is:

Field Required Default Meaning
cluster yes - Cluster identifier available to wrapper commands.
namespace yes - Namespace containing the target pod.
pod_label yes - Label selector that must match exactly one running pod.
remote_parent_path yes - Absolute POSIX parent; the local directory name is appended.
local_path yes - Existing primary source directory.
container no null Explicit target container for every exec and cp; omit it for kubectl's default container.
kubectl_command no ["kubectl"] Non-empty argument array used before every Kubernetes operation.
follow_sync no [] Additional source directories mapped below the remote project.
exclude_paths no [] Path-name and glob exclusions applied to full and sparse sync.
compress_threshold no 100 Positive legacy-compatible tuning value.
max_workers no 10 Maximum sparse-watch worker count.
debug no false Legacy-compatible diagnostic setting.
show_concurrency no false Legacy-compatible concurrency display setting.
no_watch no false Exit after the startup full mirror.
skip_verify no false Skip the interactive configuration confirmation.
debounce_seconds no 1.0 Positive watch-event debounce interval.
prune no true Legacy-compatible setting; startup remains an authoritative mirror.
upload_chunk_count no 6 Upper bound used when scheduling archive shards.
target_chunk_size_mb no 64 Positive target size for startup archive shards.

cluster, namespace, pod_label, remote_parent_path, and local_path must be present and non-empty. Boolean and positive-number fields are validated strictly.

follow_sync maps directories outside the primary tree into named children of the remote project:

{
  "follow_sync": [
    {
      "local_path": "/Users/me/src/shared-library",
      "alias": "shared",
      "create_symlink": true
    }
  ]
}

The remote mapping is /workspace/my-service/shared in this example. When alias is omitted or blank, the follow directory name is used. Aliases must be single safe path segments and must be unique.

With create_symlink: true, sync2pod also manages <local_path>/shared -> /Users/me/src/shared-library before synchronization. It reuses an exact existing symlink but refuses to overwrite a file, directory, or conflicting symlink. Set create_symlink to false to keep only the remote mapping.

Synchronization behavior

Every normal start performs an authoritative compressed full mirror before watch mode becomes active. Files are archived locally, copied in a bounded number of shards, verified, extracted into staging directories, and switched into place only after all mappings are ready.

Warning: the remote project path is a mirror target, not a merge destination. Startup replacement and later delete events remove remote content that is not present locally. Do not point remote_parent_path/local-directory-name at valuable pod data.

While watching, sync2pod batches filesystem events and applies sparse file, subtree, and deletion actions. Independent work can run concurrently up to max_workers; conflicting ancestor and descendant operations remain ordered. The selected pod is fixed for the session, so restart the command after the pod is replaced.

Each project also has a local single-run manager. A normal sync holds the persistent nonblocking lock ~/.sync2pod/<project>/manager.lock and atomically publishes its PID, selected pod, target settings, and random owner token in state.json. A second run for the same project is refused. This is a local safeguard only; it does not coordinate across machines.

Unconfigured symlinks are archived as symlinks and are never followed outside the configured roots. Sparse updates reject symlink paths that could escape a source directory. Device files and other unsupported special files are not a supported synchronization mechanism.

Command-line interface

Both console names accept the same legacy-compatible flags:

Flag Behavior
--init-config Create a project configuration. Requires --local-path.
--list-projects List configured projects.
--project NAME Select the project configuration.
--local-path PATH Source directory used during initialization.
--force Accepted for compatibility; startup already performs a full mirror.
--skip-verify Skip the interactive confirmation for this invocation.
--dry-run Build local archives and report mappings, counts, bytes, shards, and target without pod operations or symlink mutation.
--status Print the local manager state for the selected project without loading its sync configuration.
--stop Create a cooperative stop request for the selected project's active local manager.

For unattended runs, set skip_verify in the project configuration or pass --skip-verify.

--status and --stop are useful even if the configuration was removed while a previous sync is still running:

sync2pod --project demo --status
sync2pod --project demo --stop

--stop is cooperative: it requests that the active sync instance stop itself and run its normal cleanup. It never signals a PID from a state file.

Pod selection fails before local managed symlinks or remote work are changed when the selector matches zero or more than one Running Pod. Narrow the label selector until it identifies exactly one target.

Pod requirements

The local machine must provide the configured Kubernetes command and permission to list pods, execute commands, and copy files in the selected namespace. The target container must provide:

sh bash find tar md5sum rm mkdir mv cat

The process also needs write permission for the remote parent, staging paths, and final project path.

mtools and embedded backends

mtools can keep project JSON in its own database instead of ~/.sync2pod. It launches the compatibility command with:

SYNC2POD_CONFIG_BACKEND=utools-db
SYNC2POD_CONFIGS_JSON={"project-name": { ... project configuration ... }}

When this backend saves configuration, stdout contains one machine-readable line prefixed with __SYNC2POD_DB_UPDATE__ . Watch progress is emitted on stderr with __SYNC2POD_PROGRESS__ and the JSON keys pending, uploading, session_done, and session_failed. Embedders should consume these control lines and keep them out of user-facing logs.

The public Python entry point is also available:

from sync2pod import run

run(project_configuration)

Safety boundaries and non-goals

  • sync2pod synchronizes into one already-running pod; it does not deploy, restart, or supervise workloads.
  • It does not provide bidirectional synchronization, conflict resolution, or a durable cross-machine journal.
  • It does not automatically reselect pods during a watch session.
  • It does not interpret shell strings in kubectl_command.
  • It does not promise preservation of remote-only files inside the mirror target.

Troubleshooting

  • No running pod found: verify namespace, pod_label, and the configured command by running its equivalent get pods request manually.
  • Missing required commands: add the named tools to the target container; minimal images often omit bash, tar, or md5sum.
  • Permission denied: verify local read access and remote write access, including the configured parent directory.
  • Configuration confirmation blocks automation: pass --skip-verify or set skip_verify to true.
  • Follow symlink conflict: remove or rename the conflicting local path, or set create_symlink to false for that follow mapping.
  • Pod restarted: stop and relaunch sync2pod so it selects the replacement running pod and performs a fresh authoritative mirror.

Development

uv sync --all-groups
uv run pytest -m "not performance" --cov=sync2pod --cov-report=term-missing
uv run pytest -m performance
uv build

License

Licensed under the Apache License 2.0.

Metadata

Release files for sync2pod 0.1.5

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

Source distribution (sdist)

Source distribution for sync2pod 0.1.5
File Size Uploaded Provenance
sync2pod-0.1.5.tar.gz 118.1 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 178.8 kB

Release files / sync2pod-0.1.5.tar.gz

Download URL sync2pod-0.1.5.tar.gz
Size 118.1 kB
Tags Source
SHA-256 checksum
How to use checksums
8cb3ac6bcb69a619c2e209ee1c4552031b765d7de6857e8264b8b0693782b085
BLAKE2b-256 checksum
How to use checksums
4c43928cdb8dd7dce55534a111bc2b84e24b99acd59c6656e746255682b46243
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / sync2pod-0.1.5-py3-none-any.whl

Download URL sync2pod-0.1.5-py3-none-any.whl
Size 60.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
14fcf7f0674b6452ba80718c07ae951be026f08b769566700194ec5ea8cd7c1f
BLAKE2b-256 checksum
How to use checksums
8fb9eb75ec5ed7869b73c917b86cbb6183cfddf849e510bda5ccf290b3e00668
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for sync2pod 0.1.5

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.

Release history Release notifications | RSS feed

This release

0.1.5 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