Known limits
The implementation preserves Compose behavior by running Compose itself, but a remote isolated host cannot reproduce every laptop-host feature. Supported local inputs are validated before Kubernetes resource creation. Unsupported keys below produce errors naming their Compose path; they are not converted into partial Kubernetes equivalents.
Verification boundaries
Section titled “Verification boundaries”The public test suite separates local mocked/browser tests from opt-in disposable Docker tests. Neither proves every cluster’s admission, CSI provisioning, CNI packet path or behavior under a long production workload. Administrator-reviewed live acceptance remains specific to the target. PVC deletion alone is not evidence that a backing cloud volume was deleted.
See verification and test lanes. A passing fixture is not complete acceptance of an application’s business behavior.
Refused Compose features
Section titled “Refused Compose features”| Compose field or input | Reason / supported alternative |
|---|---|
include, services.*.extends | File indirection can hide path origins; list local overlays in compose.files instead. |
networks.*.external, volumes.*.external, configs.*.external, secrets.*.external | Resources from another engine are absent from the isolated engine. |
Nonstandard network/volume driver | Only ordinary bridge networks and local Docker volumes are supported. |
volumes.*.driver_opts | Host paths, devices, NFS, and other external storage cannot be reproduced by mirroring. |
services.*.devices, device_cgroup_rules, gpus, device reservations | Laptop hardware and host devices are not available in the remote pod. |
external_links, volumes_from, use_api_socket, provider, models | These rely on external containers, provider integration, or host-coupled resources. |
Host or container: values for network_mode, pid, ipc, uts, cgroup; cgroup_parent | Host/external-container namespaces and cgroup paths differ on the remote engine. |
Non-rprivate bind propagation | Shared/slave propagation cannot reproduce the laptop’s mounts. |
Bind paths outside the configuration root, .git paths, missing required sources | The mirror is deliberately restricted to existing sources beneath the configuration root. |
| Symlinks and special files inside synchronized sources | Version 1 supports regular files and directories only, including when a symlink points inside the worktree. |
Remote build.context, unsupported remote additional_contexts | Use local contexts; Compose service: and docker-image:// additional contexts are allowed. |
build.ssh, build.network: host | SSH-agent forwarding and laptop host networking are unavailable. |
UDP/SCTP published ports or non-loopback/specific-host ports.host_ip | Kubernetes port-forward provides TCP streams; version 1 accepts IPv4 wildcard or loopback publications. |
Published daemon port 2375, including ranges covering it | Reserved for the isolated engine’s loopback Docker API. |
| Ambiguous publications or multiple active service replicas | Each forwarded service target must resolve to one observed TCP publisher. Target-only, zero and ranged publishers are resolved after startup; changed mappings require up --refresh. |
Invalid or unknown Compose keys are also rejected by Compose’s own configuration parser. Existing environment-backed BuildKit secrets are left to Compose; they are not blanket-refused as if they were host bind paths. Normal Compose configs/secrets with local file sources receive the initial mirror.
A selected worktree cannot be /, a protected system directory, or an unsafe daemon mirror path. The mirror helper also refuses root paths containing : or a newline. Normal source filenames can include spaces, newlines, Unicode, and shell metacharacters.
Operational behavior to account for
Section titled “Operational behavior to account for”- Numeric ownership is preserved. UID/GID values and POSIX modes are copied directly, without mapping account names or applying Docker Desktop’s filesystem-sharing translations. Configure a service’s user consistently with its bind directory ownership. Extended attributes and platform-specific ACLs are not replicated.
- One-way synchronization. Remote edits do not flow back to the laptop. A later local edit overwrites that tracked remote file. Remote-only files are retained unless the environment is deleted. A remote symlink blocking a tracked target fails sync. A transfer is fully extracted before application, but application across multiple files is not a transaction; an error can leave earlier writes visible with the old baseline retained for retry.
- No filesystem watcher guarantee. Bind sync polls and scans requested paths, then transfers changed content. Large trees and files can exceed the usual interactive latency. The initial mirror must finish before startup. Keep generated dependencies outside a broad bind if they are unnecessary; Podgrove does not silently ignore
node_modules, caches, or virtual environments because Compose may require them. - Bind permissions use Linux IDs. Sync preserves numeric UID/GID and file modes. An owner-only Mac source file can be unreadable to a different non-root container user. Development sources need readable modes or a compatible container UID; Podgrove does not silently broaden their permissions.
- Compose watch is explicit. Without
develop.watch, image-only source files do not update through Compose watch. Bind sources still use Podgrove’s mirror. An application’s reload behavior remains its own responsibility. A declared watch rebuild action can rebuild an image. - Build filtering belongs to the project. The Docker client’s normal context upload and
.dockerignoreapply to builds. Podgrove’s.gitexclusion covers its bind/config/secret mirror; it does not rewrite Docker build contexts. - Idle tracking is specific. Bind/config/secret changes, edits beneath declared Compose watch paths, and successful
status,logs, andexeccommands count as activity. Browser requests and test traffic do not extend TTL. A long-running test can need a longer TTL. - Offline cleanup needs an external process. The local supervisor cannot reap while the laptop is asleep or disconnected. Run the explicit-namespace reaper separately if this is required; no cluster controller or webhook is installed automatically. A remote reaper can use TTL/MR metadata but cannot delete unavailable laptop files.
- Worktree removal needs local evidence. Orphan reaping requires a matching validated state record on the machine running the reaper, a missing worktree root, and an existing ordinary parent directory. A missing parent/mount is not deletion evidence; a replaced non-directory is refused. Missing local state prevents this inference.
reap --environmentnarrows the target but does not force removal of an otherwise active environment. - Local inventory has a defined scope.
status --alllists saved environments for the resolved explicit context and namespace. It does not discover unknown cluster environments or query every stack’s current health. Damaged/foreign state is reported rather than trusted. - The browser dashboard is read-only and local. It uses the same local-inventory boundary, labels saved health as a snapshot, and needs existing session/read access for live details. It does not extend TTL or install infrastructure. Snapshot and live-log buffers are bounded; live connections last up to five minutes and resuming starts a fresh tail, with possible gaps or duplicates. Replaced containers are not followed automatically. Common credential patterns are redacted; arbitrary application-log secrets can still appear. Resource allocations are not live usage metrics. See web.md.
- Dashboard addresses are saved forwards. The Endpoints tab filters by service/target/local port, but does not probe endpoint reachability or infer service health from an address. Bounded saved lists report omitted entries.
- Configuration declarations are not authorization proof. The Configuration page reads eight exact namespaced objects: the provisioning marker and seven known ServiceAccount/Role/RoleBinding declarations. It never reads Namespace or cluster RBAC objects. Missing and inaccessible are distinct; named Role/Binding declarations do not establish effective permissions or prove that bootstrap was installed. The page makes no permission-review POSTs and never expands its namespace scope through discovery.
- Current YAML is separate from runtime state. Dashboard configuration inspection shows safe validated fields without applying them. It accepts only an owned regular, non-hard-linked file beneath the worktree, with no symlink traversal, a 64 KiB cap and bounded YAML parsing. Aliases, custom tags, duplicate keys and invalid values are refused. Referenced Compose/env/secret files are not opened. Unavailable current metadata does not erase saved runtime settings.
- Successful cleanup removes local logs.
downand a reaper with matching local state remove the owned state, log, temporary files, socket, and released lock after cluster cleanup. Save diagnostic logs first. Failed cleanup retains state for retry; other environments’ files and unrelated Docker caches are not pruned. - Cleanup cannot repair a stuck platform.
down --timeoutdefaults to a 120-second Kubernetes cleanup budget after local shutdown. A timeout reports the last observed remaining objects and preserves retry state; an incomplete inventory never proves absence. The PVC stays until the engine controller and Pods are gone. Podgrove does not force-delete them, remove storage finalizers or repair nodes; resolve the blocking condition and rerun scopeddown. - Storage reclamation depends on the provisioner. A new PVC requires an explicit administrator-approved class, but Podgrove cannot inspect StorageClass/PV metadata or reclaim policy with its namespace-only access. The platform must verify backing-volume cleanup independently; a removed PVC is not proof of deletion. Historical acceptance used separately authorized inventory checks. Podgrove never removes storage finalizers or modifies a class.
- MR support is GitLab.com only. MR URLs must be provided explicitly. GitLab API failures leave environments intact; there is no implicit Git branch/MR discovery or support for other Git hosts.
- Networking needs platform verification. Bootstrap selects only Podgrove-managed Pods; unrelated/unlabelled CI Pods are unselected.
network.pod_to_poddefaults todisabled;openallows all ports between Pods labelled as Podgrove-managed in the engine’s own namespace and any listed innetwork.namespaces(a self-labelled Pod in a listed namespace joins too, so list only Podgrove-managed namespaces), whileselectedrequires both endpoints in selected mode, matching declarations and fixed wildcard TCP publications. Mixed open/selected does not grant a link. Loopback, target-only/zero/ranged ports and ambiguous replicas cannot be exposed in selected mode. Namespaces are exact; worktree globs use reported stable names. Runtime needs namespaced Pod and ConfigMapget/listaccess in referenced peer namespaces; offline validation cannot prove remote consent or reachability. Reported peer DNS currently assumescluster.local; custom suffixes are unsupported. DNS and filtered public IPv4 HTTP(S) retain separate rules, andnetwork.blocked_cidrsdoes not override explicit peer allowances. Other policies can widen access; node-local exceptions, NAT and existing-connection behavior vary by CNI. Namespace credentials can alter policies, so this is not tamper-resistant tenant isolation. See network configuration and connection examples. - Legacy links need deliberate retirement. Nonempty top-level
connectis refused. Its source-owned target ingress could bypass the new modes, so retained legacy connection resources must be retired with the recorded old version before using the new declaration model. Scopeddowndeletes that environment’s PVC data; coordinate and preserve data first. No automatic compatibility grant or cleanup shim is provided. See migration. - Engine pods have a controller. A single-replica StatefulSet recreates the engine pod with the same PVC. Startup can retry build/start after a verified Pod replacement, at most twice under the original controller/PVC anchor. Replacement after startup requires explicit
up; application-forward recovery does not adopt a replacement Pod. Per-engine PDBs and autoscaler annotations cover supported voluntary disruption, not forced deletion or node loss. Cluster policies can terminate or deny controller-managed pods; Podgrove does not exempt itself from those policies. - Storage initialization has a separate deadline.
up --init-timeoutdefaults to 300 seconds after the initializer starts, counting an available valid Kubernetes start timestamp rather than granting a fresh budget on every invocation. The larger startup/build timeout does not extend it. Failure retains the Pod/PVC and reports observed context; the timeout alone does not establish a storage, node or application root cause. - Recovery is bounded. Dropped application forwards are monitored and retried on their original ports with ownership checks. Idle sync connections get three recovery attempts against the original engine and verified helper, with a matching last-acknowledged baseline required. Uncertain transfers and exhausted sync retries pause sync while healthy forwards remain usable; inspect the mirror before
up --refresh. Application-forward retry exhaustion or a failed Compose watch needs an explicitupreconnect. A failed individual Docker API connection is reset without replaying that request or shutting down unrelated connections. There is no unlimited hidden retry loop. - Docker API ownership is revalidated. New Pods use a verified downward-API UID guard on every exec connection and full ownership checks every 30 seconds. UID replacement is rejected immediately; label/controller drift is caught by the periodic check. Unavailable ownership reads block new API requests; existing streams have a 120-second maximum proof age. Confirmed replacement is recorded with the old/new Pod and StatefulSet UIDs, including during failed builds. Legacy Pods without that binding use full reads on every connection and incur more API latency. Exec permissions are required even when no user
execcommand is requested. - Readiness uses declared behavior. A running service with no healthcheck counts as ready. Successful exited jobs are accepted when referenced by
service_completed_successfully; other stopped services are unhealthy. - Allocation is checked, not permanently reserved. Free local ports are checked before tunnels bind them. A concurrent process can still win that race; startup then fails clearly. Explicit local ports reduce flexibility and can collide across worktrees.
- Existing environments retain their spec. Reconnecting does not resize the PVC or roll the StatefulSet to apply a new size/storage flag; incompatible controller settings are refused. Recreating with
downdiscards data. - Requests affect scheduling. Preset memory requests equal limits (
2Gi,8Gi, or16Gi); custom budgets may differ. Kubernetes schedules against effective requests, while admission can add defaults or enforce quotas. Low instantaneous usage does not imply enough capacity for another request. Podgrove does not resize cluster node pools. - Namespace selection is required. Cluster operations require
cluster.namespaceor an explicit--namespace; there is no implicitdefaultor generated namespace fallback.cluster.namespace_modedefaults toshared. Setworktreeto derive<base>-wt-<identity>; long bases are shortened with a hash. Both modes require a preexisting namespace and administrator-provided access. Namespace mode is separate from shared/tainted node placement. - Bootstrap resources survive normal cleanup.
downandreapretain namespaces and bootstrap resources in both current modes, deleting only owned environment resources. New shared targets remain shared even when their names beginwt-. Legacyexclusiverecords also retain the namespace. No product cleanup path reads or deletes Namespace objects. - Bootstrap generation is local.
podgrove bootstrap --output <new-folder>writes five files/nine namespaced objects without inspecting the existing namespace or its occupants. It creates no Namespace, cluster RBAC, quota, LimitRange or admission policy. An approved existingdefaultis supported; reservedkube-*targets are refused. Worktree mode needs a prepared namespace and bundle for each actual target. An administrator reviews same-name collisions and removes only the retained setup when retiring it. StorageClass is not required for generation. - Local session support targets macOS/Linux. State locking and supervision use Unix facilities. Windows support is not implemented.
Security prerequisites
Section titled “Security prerequisites”A privileged inner Docker engine shares its node’s kernel and is not a hardened boundary against untrusted workloads. Eligible shared Linux nodes are supported by default; node_mode: tainted optionally limits placement using tainted_nodes.selector and its configured taint key/value/effect. Both modes keep separate engines/PVCs, provisioning-marker/resource ownership, NetworkPolicy, and resource limits. Neither mode supports Fargate or EKS Auto Mode. Podgrove does not create node pools, grant itself cluster-admin, or alter node labels and taints.
The platform must prepare the target namespace and approve admission, scheduling, storage and the trust relationship with any existing CI workloads. Podgrove uses only namespaced workload APIs, exec/port-forward, and exact dashboard SA/RBAC reads. It does not inspect or modify Namespace, node, StorageClass, PV, cluster-RBAC or admission objects. Both shared and tainted scheduling modes use that boundary. The provisioning marker verifies the intended namespace mode without reading Namespace metadata.
Generated workload permissions are namespace-wide; Kubernetes RBAC does not restrict them by ownership labels. Pod creation can expose other namespace resources or ServiceAccounts, and direct credentials can bypass the CLI’s ownership safeguards. Choose an approved trust boundary rather than treating the generated Role as malicious-tenant isolation. Existing admission and network policies still apply. Use test/development secrets and review local env files before running a remote stack.