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 fixedtask_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 mappings and managed symlinks
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 equivalentget podsrequest manually. - Missing required commands: add the named tools to the target container;
minimal images often omit
bash,tar, ormd5sum. - Permission denied: verify local read access and remote write access, including the configured parent directory.
- Configuration confirmation blocks automation: pass
--skip-verifyor setskip_verifytotrue. - Follow symlink conflict: remove or rename the conflicting local path, or
set
create_symlinktofalsefor 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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sync2pod-0.1.5.tar.gz | 118.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.