โก A Helm plugin to run any command (helm/kubectl/etc) inside a Kubernetes cluster
- Why?
- Requirements
- Installation
- Daemon Mode
- Usage
- Environment Variables
- How It Works
- RBAC / Cluster Resources
- Examples
- Purge
- Development
๐ก Network Latency Problem
When helm runs commands from your local machine, network latency to distant Kubernetes clusters can significantly slow down operations, especially with large releases containing many manifests.
helm-in-pod solves this by running Helm commands directly inside the cluster, minimizing network latency between the client and Kubernetes API.
| Feature | Description |
|---|---|
| ๐โโ๏ธ Fast Execution | Run commands inside the cluster for minimal latency |
| ๐ง Any Command | Execute helm, kubectl, or any other command |
| ๐ฆ Repository Sync | Automatically copies all host Helm repositories |
| ๐ File Transfer | Copy files/folders from host to pod |
| ๐ Environment Variables | Set custom environment variables in the pod |
| ๐ณ Custom Images | Use any Docker image for execution |
| ๐พ Volume Mounts | Mount PVCs, secrets, configmaps, and more into pods |
| ๐ Dry Run | Preview pod specs as YAML before creating anything |
| ๐ Keep Pod | Retain the pod after failure for interactive debugging |
- ๐ฏ Helm 4 installed on host machine
โ ๏ธ Helm 3 users: v0.8.1 is the last supported version. Install it with:helm plugin install --version v0.8.1 https://github.com/Noksa/helm-in-podSee #20 for details.
| OS | Architecture |
|---|---|
| Linux | amd64, arm64 |
| macOS | amd64, arm64 |
| Windows | amd64, arm64 |
๐ฅ Quick Install/Update
# Install or update the plugin
(helm plugin uninstall in-pod || true) && helm plugin install oci://ghcr.io/Noksa/helm-in-pod/in-pod:0.9.0๐ก Replace
0.9.0with any version from the releases page
The plugin tarball is signed with PGP. Helm 4 verifies the signature automatically if you have the public key imported into your GPG keyring:
curl -sSL https://raw.githubusercontent.com/Noksa/helm-in-pod/main/public-key.asc | gpg --import
gpg --export > ~/.gnupg/pubring.gpg๐ฅ Run multiple commands without pod recreation overhead!
# Start once
helm in-pod daemon start --name dev --copy-repo
# Execute many times - INSTANT! โก
helm in-pod daemon exec --name dev -- "helm list -A"
helm in-pod daemon exec --name dev -- "helm upgrade myapp ..."
# Or open an interactive shell ๐
helm in-pod daemon shell --name dev
# Check on your daemons ๐
helm in-pod daemon list
helm in-pod daemon status --name dev
# Stop when done
helm in-pod daemon stop --name devMuch faster for multiple operations โ pod startup runs once, not per command. Perfect for CI/CD, interactive development, and batch deployments.
๐ก Set
HELM_IN_POD_DAEMON_NAMEenvironment variable to avoid repeating--nameon every command. See DAEMON.md for details.
๐ Read Full Daemon Mode Documentation
helm in-pod --helphelm in-pod exec [FLAGS] -- "COMMAND"๐ก
runis an alias forexec:helm in-pod run [FLAGS] -- "COMMAND"
| Flag | Description |
|---|---|
--verbose-logs |
Enable debug logs |
--timeout |
Gracefully terminate command after duration (default: 2h) |
โ ๏ธ Note: Forexecanddaemon start, the plugin adds 10 minutes to the specified--timeoutinternally for pod operations (startup, file copy, etc.). For example,--timeout 2hresults in a total pod lifetime of 2h10m. Indaemon exec, the timeout applies directly to command execution with no additional overhead. See DAEMON.md for details.
| Flag | Short | Description |
|---|---|---|
--image |
-i |
Docker image to use (run helm in-pod exec --help for current default). Overrides HELM_IN_POD_IMAGE env var |
--cpu-request |
Pod's CPU request (default: 1100m) |
|
--cpu-limit |
Pod's CPU limit (default: 1100m) |
|
--memory-request |
Pod's memory request (default: 500Mi) |
|
--memory-limit |
Pod's memory limit (default: 500Mi) |
|
--create-pdb |
Create PodDisruptionBudget (default: true) | |
--tolerations |
Pod tolerations for node taints | |
--node-selector |
Pod node selectors for node targeting | |
--host-network |
Use host network in pod | |
--run-as-user |
User ID for security context | |
--run-as-group |
Group ID for security context | |
--labels |
Additional labels on the pod | |
--annotations |
Additional annotations on the pod | |
--image-pull-secret |
Image pull secret for private repositories | |
--pull-policy |
Image pull policy (default: IfNotPresent) |
|
--volume |
Mount volumes in the pod (repeatable). Format: type:name:mountPath[:ro]. Types: pvc, secret, configmap, hostpath |
|
--service-account |
Service account for the pod (default: helm-in-pod) |
|
--dry-run |
Print the pod spec as YAML without creating anything | |
--active-deadline-seconds |
Maximum duration in seconds the pod is allowed to run. Kubernetes terminates the pod once this deadline is exceeded, regardless of whether the client is still connected. Useful to avoid orphaned pods in CI/CD pipelines. 0 means no deadline (default) |
|
--keep-pod |
Keep the pod alive after exec completes (for debugging). The pod is removed when helm in-pod purge is run |
|
--startup-timeout |
How long to wait for the pod to become ready (default: 5m). Increase on clusters with slow image registries |
|
--privileged |
Run the container in privileged mode (requires cluster policy to allow it) |
โ ๏ธ Deprecated Flags
The following flags still work but are deprecated and will be removed in a future release:
| Deprecated Flag | Replacement |
|---|---|
--cpu |
--cpu-request and --cpu-limit |
--memory |
--memory-request and --memory-limit |
When using a deprecated flag, its value is applied to both the request and limit. You cannot combine deprecated flags with their replacements (e.g., --cpu with --cpu-request will error).
| Flag | Short | Description |
|---|---|---|
--copy |
-c |
Copy files/folders from host to pod |
--env |
-e |
Set environment variables |
--subst-env |
-s |
Substitute environment variables from host |
--copy-repo |
Copy existing Helm repositories to pod (default: true) | |
--update-repo |
Update specified Helm repositories | |
--copy-attempts |
Retry count for copy actions (default: 3) | |
--update-repo-attempts |
Retry count for repo update actions (default: 3) | |
--copy-from |
Copy files/dirs from pod to host after execution (repeatable). Format: /pod/path:/host/path |
|
--env-file |
Read environment variables from a file (KEY=VALUE format, supports comments and quotes). Repeatable. Explicit --env flags take precedence |
|
--suppress-secrets |
-q |
Mask values of --set, --set-string, --set-file, --set-json flags in log output |
| Variable | Description |
|---|---|
HELM_KUBECONTEXT |
Override the Kubernetes context used by the plugin. When set, the plugin connects to this context instead of the current default. |
HELM_IN_POD_DAEMON_NAME |
Default daemon name for daemon subcommands, so you can omit --name. See DAEMON.md. |
HELM_IN_POD_IMAGE |
Default image for all pods created by the plugin. Equivalent to passing --image on every command. An explicit --image flag always takes precedence. |
HELM_IN_POD_NAMESPACE |
Kubernetes namespace for all plugin resources (pods, ServiceAccount, ClusterRoleBinding, PDBs). Defaults to helm-in-pod. |
๐ Execution Flow
When you run helm in-pod exec, the following happens:
- ๐๏ธ Pod Creation: Creates a new
helm-in-podpod in thehelm-in-podnamespace - ๐ Repository Sync: Copies all existing Helm repositories from host to pod (Helm 4 sync is detected and handled automatically)
- ๐ Repository Updates: Fetches updates for specified repositories
- ๐ File Transfer: Copies specified files/directories to the pod
โถ๏ธ Command Execution: Runs your specified command inside the pod
The plugin propagates the exit code from the executed command. If the command inside the pod exits with code N, helm in-pod also exits with code N. This makes the plugin safe to use in CI/CD pipelines where non-zero exit codes signal failure.
# If "helm upgrade" fails with exit code 1, helm in-pod also exits with code 1
helm in-pod exec -- "helm upgrade myapp repo/chart"
echo $? # prints the exit code from the command inside the podWhen the plugin runs for the first time, it automatically creates the following Kubernetes resources:
| Resource | Name | Details |
|---|---|---|
| Namespace | helm-in-pod |
Dedicated namespace for all plugin pods |
| ServiceAccount | helm-in-pod |
Created in the helm-in-pod namespace |
| ClusterRoleBinding | helm-in-pod |
Binds the ServiceAccount to the cluster-admin ClusterRole |
โ ๏ธ Security Note: The pod runs withcluster-adminprivileges. This grants full access to all cluster resources. Make sure this is acceptable in your environment before using the plugin.
These resources are shared by both exec and daemon modes. Use helm in-pod purge --all to remove them (see Purge).
Get all pods
helm in-pod exec -- "kubectl get pods -A"List Helm releases
helm in-pod exec -- "helm list -A"Simple installation
# Add repository on host
helm repo add bitnami https://charts.bitnami.com/bitnami --force-update
# Install from pod
helm in-pod exec --update-repo bitnami -- \
"helm install -n bitnami-nginx --create-namespace bitnami/nginx nginx"Installation with custom values
helm repo add bitnami https://charts.bitnami.com/bitnami --force-update
# Copy values file and install
helm in-pod exec \
--copy /home/alexandr/bitnami/nginx_values.yaml:/tmp/nginx_values.yaml \
--update-repo bitnami -- \
"helm upgrade -i -n bitnami-nginx --create-namespace bitnami/nginx nginx -f /tmp/nginx_values.yaml"
โ ๏ธ Important: Use the pod path (/tmp/nginx_values.yaml) in the helm command, not the host path
Using environment variables
helm in-pod exec \
-e "HELM_DRIVER=sql" \
-e "HELM_DRIVER_SQL_CONNECTION_STRING=postgresql://helmpostgres.helmpostgres:5432/db?user=user&password=password" \
--copy /home/alexandr/bitnami/nginx_values.yaml:/tmp/nginx_values.yaml \
--update-repo bitnami -- \
"helm upgrade -i -n bitnami-nginx --create-namespace bitnami/nginx nginx -f /tmp/nginx_values.yaml"Using host environment substitution
# Set environment variables on host
export HELM_DRIVER=sql
export HELM_DRIVER_SQL_CONNECTION_STRING=postgresql://helmpostgres.helmpostgres:5432/db?user=user&password=password
# Use them in pod
helm in-pod exec \
-s "HELM_DRIVER,HELM_DRIVER_SQL_CONNECTION_STRING" \
--copy /home/alexandr/bitnami/nginx_values.yaml:/tmp/nginx_values.yaml \
--update-repo bitnami -- \
"helm upgrade -i -n bitnami-nginx --create-namespace bitnami/nginx nginx -f /tmp/nginx_values.yaml"Using host network
# Run with host network for network troubleshooting
helm in-pod exec --host-network -- "kubectl get pods -A"
# Access services on host network
helm in-pod exec --host-network -- "curl http://localhost:6443"
# Test DNS from host perspective
helm in-pod exec --host-network -- "nslookup kubernetes.default.svc.cluster.local"Running on tainted nodes
# Tolerate all taints
helm in-pod exec --tolerations "::Exists" -- "helm list -A"
# Tolerate specific key with any effect
helm in-pod exec --tolerations "key=:NoSchedule:Exists" -- "helm list -A"
# Tolerate specific key-value pair
helm in-pod exec --tolerations "key=value:NoSchedule:Equal" -- "helm list -A"
# Multiple tolerations
helm in-pod exec \
--tolerations "node-role.kubernetes.io/control-plane=:NoSchedule:Exists" \
--tolerations "dedicated=special:NoExecute:Equal" -- \
"helm list -A"Targeting specific nodes with node selectors
# Run on nodes with specific label
helm in-pod exec --node-selector "disktype=ssd" -- "helm list -A"
# Run on control plane nodes (empty value)
helm in-pod exec --node-selector "node-role.kubernetes.io/control-plane=" -- "helm list -A"
# Multiple node selectors
helm in-pod exec \
--node-selector "disktype=ssd,environment=production" -- \
"helm list -A"
# Combine with tolerations for control plane
helm in-pod exec \
--node-selector "node-role.kubernetes.io/control-plane=" \
--tolerations "node-role.kubernetes.io/control-plane=:NoSchedule:Exists" -- \
"helm list -A"Helm diff with custom configuration
helm in-pod exec \
-e "HELM_DIFF_NORMALIZE_MANIFESTS=true,HELM_DIFF_USE_UPGRADE_DRY_RUN=true,HELM_DIFF_THREE_WAY_MERGE=true" \
--copy /home/alexandr/bitnami/nginx_values.yaml:/tmp/nginx_values.yaml \
--update-repo bitnami -- \
"helm diff upgrade -n bitnami-nginx --create-namespace bitnami/nginx nginx -f /tmp/nginx_values.yaml"Custom Docker images
# Specific Helm version
helm in-pod exec -i "alpine/helm:3.12.1" -- "helm list -A"
# Custom image with additional tools
helm in-pod exec -i "alpine:3.18" -- "apk add curl --no-cache && curl google.com"Mount a PersistentVolumeClaim
helm in-pod exec --volume pvc:my-claim:/data -- "ls /data"Mount secrets and configmaps (read-only)
helm in-pod exec \
--volume secret:my-secret:/etc/creds:ro \
--volume configmap:my-cm:/etc/config:ro -- \
"cat /etc/creds/password"Mount host path
helm in-pod exec --volume hostpath:/var/log:/host-logs:ro -- "ls /host-logs"Multiple volumes in one command
helm in-pod exec \
--volume pvc:data:/data \
--volume secret:creds:/etc/creds:ro \
--volume configmap:app-config:/etc/config -- \
"helm upgrade myapp repo/chart -f /etc/config/values.yaml"Use a custom service account
# Run with a specific service account instead of the default helm-in-pod
helm in-pod exec --service-account my-sa -- "helm list -A"
# Combine with daemon mode
helm in-pod daemon start --name dev --service-account ci-deployer --copy-repoPreview pod spec before creating
# Print the pod spec as YAML without creating anything
helm in-pod exec --dry-run -- "helm install myapp repo/chart"
# Preview a daemon pod spec with custom configuration
helm in-pod daemon start --name dev --dry-run \
--volume pvc:data:/data \
--service-account my-sa \
--copy-repoPrevent orphaned pods in CI/CD pipelines
When a CI/CD job is cancelled, an SSH session closes, or a machine crashes mid-execution, the pod created by helm in-pod may be left running indefinitely. --active-deadline-seconds sets a hard time limit enforced by Kubernetes itself โ the pod is terminated once the deadline is exceeded, regardless of client connectivity.
# Terminate the pod after 30 minutes (1800s) if still running
helm in-pod exec --active-deadline-seconds 1800 -- "helm upgrade --install myapp repo/chart"
# Use in CI/CD pipeline to protect against hung jobs
helm in-pod exec \
--active-deadline-seconds 3600 \
--copy-repo \
-- "helm upgrade --install myapp repo/chart -f values.yaml"
# Preview the pod spec with the deadline set
helm in-pod exec \
--dry-run \
--active-deadline-seconds 1800 \
-- "helm upgrade --install myapp repo/chart"
# Also works with daemon mode
helm in-pod daemon start --name ci-daemon \
--active-deadline-seconds 7200Note:
--active-deadline-secondslimits total pod lifetime. For limiting command execution time only, use--timeoutinstead. Both flags can be combined:--timeoutkills the command while--active-deadline-secondskills the pod.
Copy files back from pod after execution
# Copy a single file
helm in-pod exec \
--copy-from /tmp/output.yaml:./output.yaml -- \
"helm template myapp repo/chart > /tmp/output.yaml"
# Copy multiple paths (e.g., test artifacts)
helm in-pod exec \
--copy-from /tmp/report.html:./report.html \
--copy-from /tmp/logs:/tmp/local-logs -- \
"run-tests --output /tmp/report.html --log-dir /tmp/logs"๐ก Files are copied even if the command fails โ useful for retrieving test artifacts, logs, or partial outputs.
Copy from a daemon pod
helm in-pod daemon exec --name dev \
--copy-from /tmp/diff-output.txt:./diff-output.txt -- \
"helm diff upgrade myapp repo/chart > /tmp/diff-output.txt"Inspect a pod after a failed command
When a command fails, the pod is normally deleted immediately. --keep-pod leaves it alive so you can exec in and investigate:
helm in-pod exec --keep-pod -- "helm upgrade myapp repo/chart -f values.yaml"
# Pod is still alive โ inspect it
kubectl exec -n helm-in-pod <pod-name> -- sh
# Clean up when done
helm in-pod purge๐ก The pod name is printed in the plugin output when
--keep-podis used. Kept pods persist untilhelm in-pod purgeis run.
List all daemon pods
# List all running daemon pods
helm in-pod daemon list
# Alias
helm in-pod daemon lsCheck status of a specific daemon
helm in-pod daemon status --name devDisplays: name, pod name, phase, node, age, image, helm version, and home directory.
Remove leftover pods and resources created by helm-in-pod in the cluster:
# Remove remaining pods/garbage
helm in-pod purge
# Remove everything (including namespace-level resources)
helm in-pod purge --all| Command | What it removes |
|---|---|
purge |
Pods kept via --keep-pod on the current host, their PDBs, and the ClusterRoleBinding |
purge --all |
Every pod in the namespace (regardless of host), every PDB (including orphans), and the ClusterRoleBinding |
๐ก
purge --alldoes not delete the namespace itself or the ServiceAccount.purge(without--all) is host-scoped โ it only touches pods created on the current machine.
- Go 1.26+
- Ginkgo test framework (installed automatically by
maketargets)
| Target | Description |
|---|---|
make build |
Build binary for current platform |
make test |
Run unit tests |
make test-verbose |
Run unit tests with verbose output |
make test-coverage |
Run tests with coverage report |
make lint |
Run linters and formatters |
make tidy |
Tidy go modules |
make install-local |
Build and install plugin locally for testing |
make test-e2e-full |
Full e2e flow: setup kind cluster, test, teardown |
Run make help for the full list.
