CodePier CLI
The CodePier CLI is a Kubernetes development environment hot-swap tool. It replaces running pods with local development containers, syncs your code in real-time, forwards ports, and proxies traffic — so you can develop against a live cluster as if it were local.
Managed by the App
The CodePier desktop app installs and updates the CLI automatically. You only need manual installation for headless environments (CI, remote servers) or if you prefer to manage it yourself.
Installation
macOS / Linux
curl -fsSL https://get.codepier.dev | shWindows (PowerShell)
irm https://get.codepier.dev/install.ps1 | iexDownload binaries
Pre-built binaries for every platform:
codepier-darwin-arm64— macOS Apple Siliconcodepier-darwin-x64— macOS Intelcodepier-linux-arm64— Linux ARM64codepier-linux-x64— Linux x64codepier-windows-x64.exe— Windows x64
Verify
codepier --versionConfiguration
Create a codepier.yaml file in the root of your project. This file tells the CLI how to swap your deployment, what to sync, and how to route traffic.
sync
Paths to synchronize between your local machine and the pod. Each entry maps a local path to a container path separated by a colon.
sync:
- "./src:/app/src"
- "./package.json:/app/package.json"image
The development image to use for the swapped pod. Can be a single string or an architecture-specific map.
# Single image
image: "node:20-slim"
# Architecture-specific images
image:
arm64: "myregistry/dev-image:arm64"
amd64: "myregistry/dev-image:amd64"forward
Ports to make reachable from your machine. Two forms, because there are two different things worth forwarding.
A local:remote string forwards into the pod that codepier up hot-swapped. It only means anything while a swap is running.
forward:
- "3000:3000"
- "9229:9229" # Node.js debuggerAn object forwards a cluster service to a local port, served by codepier forward. No hot swap, no proxy, no certificate — a port on localhost, which is what a connection string wants.
forward:
- service: postgres
port: 5432
localPort: 50444 # keeps clear of a local postgres
- service: redis
port: 6379
localPort: 50637
- service: typesense
port: 8108
- service: kafka
port: 9092
namespace: shared # a datastore in another namespaceservice— the service name, askubectl get svclists it.port— the port on the service.localPort— the port to bind here. Defaults toport. Worth moving for anything that also runs locally: a forwarded cluster postgres on 5432 is indistinguishable from a local one.namespace— override for this entry. Defaults to the firstnamespacesvalue.
Use this rather than proxy for anything that is not HTTP. The proxy terminates TLS and routes on a Host header; postgres opens a socket and starts talking its own binary protocol with no hostname anywhere in it, so there is nothing for a proxy to route on. Databases, caches, search indexes and brokers belong here.
namespaces
Kubernetes namespace(s) to target. A single string skips the interactive prompt. An array lets you limit the choices.
# Single namespace — skips prompt
namespaces: "staging"
# Multiple namespaces — prompts to choose
namespaces:
- "staging"
- "development"workload
Workload name(s) to target. Same behavior as namespaces — a single value skips the prompt. The legacy deployment field is still accepted as an alias.
# Single workload — skips prompt
workload: "api-server"
# Multiple workloads — prompts to choose
workload:
- "api-server"
- "worker"kind
The workload kind to target. Defaults to Deployment. For DaemonSet workloads the swap pod is pinned to a single node — you'll be prompted to pick one, or you can pass --node.
kind: "StatefulSet" # or "Deployment" (default), "DaemonSet"ignore
Gitignore-style patterns to exclude from file sync. Useful for keeping large directories like node_modules or build artifacts out of the sync.
ignore:
- "node_modules"
- ".git"
- "dist"
- "*.log"supplemental
When true, adds a new sidecar container to the pod instead of replacing the existing one. Useful when you need the original container to keep running alongside your dev environment.
supplemental: truecache
Persistent cache volumes that survive pod restarts. Ideal for package manager caches, build caches, or any directory you want to persist between sessions. You can use a plain path or a name:path format to give the cache a descriptive name.
cache:
# Named caches — the name is used as the volume identifier
- npm:~/.npm
- pip:~/.cache/pip
# Plain path caches
- /app/node_modules
- /root/.cacheworkdir
The working directory inside the container. This is where the SSH session starts.
workdir: "/app"containers
Restricts which containers can be targeted for hot-swap. Only the containers listed here will be available for selection. If omitted, all containers in the pod are available.
containers:
- "api"proxy
HTTPS reverse proxy entries. Each entry maps a local hostname to an upstream — either a Kubernetes service (port-forwarded automatically) or, when service is omitted, a port on your local machine. The CLI generates trusted certificates via mkcert and listens on port 443 by default (see proxyPort).
proxy:
# Forward to a Kubernetes service
- hostname: "api.codepier.local"
service: "api-server" # K8s service name
port: 3000
# Omit service — forward to a local port instead
# (e.g. a dev server running on your machine)
- hostname: "app.codepier.local"
port: 5173Hostnames ending in .local are automatically published via mDNS, so they resolve on your machine and across your LAN without editing /etc/hosts. The proxy forwards X-Forwarded-Host, X-Forwarded-Port, and X-Forwarded-Proto headers and supports WebSocket upgrades.
Path-prefix routes
Each entry can declare routes to send specific path prefixes to a different upstream than the host default — useful for mirroring a production ingress locally (e.g. codepier.local/auth → the API service). A prefix matches the exact path and any subpath (/auth matches /auth/github but not /authxyz). First match wins.
proxy:
- hostname: "codepier.local"
port: 3000 # default upstream: local dev server
routes:
- pathPrefix: "/auth"
service: "api-server" # /auth/* goes to the cluster API instead
port: 4000remoteHostname
Overrides the Host header sent upstream. Use it when the local listen name differs from what the cluster ingress or backend expects — e.g. listen on dev.api.codepier.local but forward as api.codepier.local. Available on both top-level entries and path routes.
proxy:
- hostname: "dev.api.codepier.local"
remoteHostname: "api.codepier.local"
service: "api-server"
port: 3000proxyPort
The HTTPS port the proxy listens on. Defaults to 443, which may require sudo — use a high port like 8443 to avoid that. Can also be set per-run with --port.
proxyPort: 8443env
Environment variables to inject into the dev container. You can set explicit values or forward variables from your local shell with from_env: true.
env:
- name: "NODE_ENV"
value: "development"
- name: "DATABASE_URL"
value: "postgresql://localhost:5432/mydb"
# Forward from your local shell
- name: "AWS_ACCESS_KEY_ID"
from_env: true
- name: "AWS_SECRET_ACCESS_KEY"
from_env: trueCommands
codepier up
The core command. Hot-swaps a Kubernetes workload (Deployment, StatefulSet, or DaemonSet) with a local development container. It scales down the original workload, creates a hot-swap pod with your chosen image, starts real-time file sync via Mutagen, and drops you into an SSH session. When you exit, the original workload is restored automatically. DaemonSet swaps are pinned to a single node — pick one interactively or pass --node.
# Interactive — prompts for namespace, workload, and container
codepier up
# Non-interactive — skip all prompts
codepier up --namespace staging --deployment api-server --container api
# DaemonSet swap pinned to a specific node
codepier up --deployment log-agent --node worker-2codepier down
Stops active hot swaps. Lists all hot-swapped deployments and lets you select which ones to restore. Each selected deployment is scaled back up and the hot-swap pod is removed.
codepier downcodepier ssh
Opens an SSH session directly into a running pod. Works with both regular and hot-swapped pods.
# SSH into the default container
codepier ssh
# SSH into a specific container
codepier ssh --container workercodepier exec
Runs a single command inside the hot-swapped pod and exits with that command's exit code. The non-interactive counterpart to ssh — it resolves the namespace, workload and container from your codepier.yaml, starts in workdir, and streams output straight through. Diagnostics go to stderr, so stdout carries only the command's own output and is safe to pipe. Ideal for scripts, CI, and coding agents.
# Run a build inside the container
codepier exec -- pnpm run build
# Exit code propagates — usable in a shell conditional
codepier exec -- pnpm test && echo "green"
# Override the working directory
codepier exec --cwd /app/packages/db -- pnpm run migrateRequires an active hot-swap — start one with codepier up first. If your codepier.yaml lists multiple namespaces, workloads or containers, pass --namespace, --deployment or --container to pick one — exec never prompts.
codepier tail
Streams output to your terminal in real-time. When a shell session from codepier up or codepier ssh is live for the workload, tail follows that session's output instead of the container's logs — which is where your dev server actually prints. Otherwise it falls back to the container's own logs.
Only the swapped container's shell is mirrored, so naming a different container with --container — a sidecar, or the workload's real container sitting beside a supplemental swap — reads that container's logs from the pod. --container-logs does the same for the swapped container itself.
# Follow the live session if there is one, else container logs
codepier tail
# Tail a specific container
codepier tail --container api
# Ignore the live session and read the container's own logs
codepier tail --container-logsThis matters because a hot-swap container's PID 1 is sleep infinity, and kubectl logs only serves PID 1's output. Anything you start in the swapped pod — a pnpm dev watcher, a test runner — writes to the exec stream, which the cluster's log collector never sees. An empty container log therefore never meant the app was down; now a second terminal can watch it directly.
Session mirroring
While an interactive session is open, the CLI tees the same bytes it paints on your terminal to ~/.codepier/sessions/<namespace>-<workload>.log. It only observes the stream, so the session stays fully interactive — Ctrl-C still reaches the process.
- ANSI colour and cursor sequences are stripped, and carriage-return redraws collapse to their final state — so a progress bar lands as one line, not a hundred frames.
- The file keeps only the most recent 2 MB, so a long-running watcher can't fill your disk.
- It is truncated when a session starts and deleted when the shell exits — its presence is the signal that a session is live, and a running
tailexits with it. - A logging failure never takes down your shell; the session continues without a mirror.
Sessions are keyed by namespace and workload, so tailing from any directory that resolves to the same workload follows the same session.
codepier proxy
Starts a local HTTPS reverse proxy based on the proxy entries in your codepier.yaml. On first run it installs a local mkcert CA into your system trust store (you'll be prompted for your password), then generates TLS certificates from it — so browsers trust the proxy without warnings. It port-forwards to each Kubernetes service with automatic reconnection, routes traffic by hostname and path prefix, publishes .local hostnames over mDNS, and supports WebSocket upgrades. A live status display shows each route's connection state.
# Start the proxy (listens on 443 by default)
codepier proxy
# Listen on a non-privileged port instead
codepier proxy --port 8443
# Start proxy and run a command once ready.
# Runs in a shell (pipes, && etc. work) and the mkcert CA is passed
# via NODE_EXTRA_CA_CERTS so child processes trust the proxy.
codepier proxy --exec "pnpm run codegen && pnpm dev"codepier forward
Forwards cluster services to local ports, based on the service entries under forward in your codepier.yaml. Each tunnel reconnects on its own when the pod behind it is replaced — a deploy, a node drain, an OOM kill — so a dev server does not have to be restarted because a pod moved. A live status display shows each one's state.
This is the plain-TCP counterpart to codepier proxy, and it exists because that command structurally cannot do this: a proxy terminates TLS and routes on a Host header, and postgres, redis, kafka and friends send neither. Reach for proxy for anything a browser opens, and this for anything your code connects to.
# Hold every service in codepier.yaml open
codepier forward
# Just the one
codepier forward --service postgres
# Bring the tunnels up, then start your dev server through them.
# Waits until every tunnel answers before handing over, so the first
# query of a run doesn't race its own database.
codepier forward --exec "pnpm dev"codepier clean
Removes orphaned file sync directories from cluster nodes. Useful for cleaning up after interrupted sessions or stale hot-swaps.
codepier cleancodepier sync-status
Displays the current Mutagen file synchronization status — which sessions are active, their sync state, and any conflicts.
codepier sync-statuscodepier mutagen
Direct passthrough to the Mutagen CLI for advanced debugging. Accepts any Mutagen subcommand. Also supports license activation.
# Run any mutagen command
codepier mutagen sync list
codepier mutagen sync monitor
# Activate a Mutagen license
codepier mutagen license activate <license-key>codepier update
Checks for the latest CLI release and self-updates. Shows version comparison and prompts for confirmation.
codepier updatecodepier uninstall
Completely removes the CLI binary and the ~/.codepier config directory from your machine.
codepier uninstallCoding Agents
A coding agent dropped into a CodePier project can't tell that your code runs in a Linux container in a cluster rather than on your machine. It installs dependencies locally, the two-way file sync mirrors the result into the pod, and the build breaks.
codepier skills writes an agent skill into your project that fixes this. The skill teaches the agent to read your codepier.yaml, translate between local and container paths, route build-critical commands through codepier exec, and leave the session lifecycle to you.
# Run from your project root, next to codepier.yaml
codepier skills
# Installs to .claude/skills/codepier/
# Overwrite an existing copy after a CLI update:
codepier skills --forceThe skill ships inside the CLI binary and is stamped with the CLI version, so you can tell at a glance when an installed copy has fallen behind. It works with any agent that reads the .claude/skills convention. Commit it so your whole team gets the same behavior.
Global Options
These flags can be passed to any command.
| Flag | Description |
|---|---|
| --config <path> | Path to kubeconfig file (default: ~/.kube/codepier) |
| --context <name> | Kubernetes context to use |
| --namespace <name> | Kubernetes namespace (skips interactive prompt) |
| --deployment <name> | Workload name (skips interactive prompt) |
| --container <name> | Container name (skips interactive prompt). If containers is set in your codepier.yaml, must be one of the allowed containers |
| --pod <name> | Pod name to target (skips prompt when multiple match) |
| --node <name> | Node to pin a DaemonSet swap to (skips interactive prompt) |
| --temp-dir | Use emptyDir instead of hostPath for sync volumes |
Examples
Getting Started
The simplest possible config. Swaps a deployment with a dev image, syncs your entire project, and caches npm packages between sessions.
workload: hello-world
namespaces:
- hello-world
image: node:24-slim
workdir: /opt/hello-world
sync:
- ".:/opt/hello-world"
ignore:
- "node_modules"
- ".git"
cache:
- npm:~/.npmNode.js API with Port Forwarding
A more complete setup for developing a Node.js service. Syncs specific source paths, forwards the app port and debugger, and uses named caches.
workload: api-server
namespaces:
- staging
image: node:20-slim
workdir: /app
sync:
- "./src:/app/src"
- "./package.json:/app/package.json"
- "./tsconfig.json:/app/tsconfig.json"
forward:
- "3000:3000"
- "9229:9229"
ignore:
- "node_modules"
- "dist"
- ".git"
- "*.log"
cache:
- npm:~/.npm
env:
- name: "NODE_ENV"
value: "development"Multi-Service with HTTPS Proxy
Route local HTTPS traffic to Kubernetes services and a local dev server, mirroring the production ingress with path routes. Run codepier proxy to start the reverse proxy, then access each service via its hostname — the .local names resolve automatically via mDNS.
namespaces: "staging"
workload: "api-server"
image: "node:20-slim"
workdir: "/app"
sync:
- ".:/app"
forward:
- "3000:3000"
ignore:
- "node_modules"
- ".git"
cache:
- "/app/node_modules"
proxyPort: 8443
proxy:
# Local dev server, with /auth handled by the cluster API
- hostname: "app.codepier.local"
port: 5173
routes:
- pathPrefix: "/auth"
service: "api-server"
port: 4000
- hostname: "api.codepier.local"
service: "api-server"
port: 3000
- hostname: "auth.codepier.local"
service: "auth-service"
port: 4000
env:
- name: "NODE_ENV"
value: "development"
- name: "API_URL"
value: "https://api.codepier.local:8443"Multi-Architecture with Supplemental Container
Use architecture-specific images and add a sidecar container instead of replacing the original. This is useful when you need the original container's processes to keep running alongside your dev environment.
namespaces:
- "staging"
- "production"
workload:
- "worker"
- "scheduler"
kind: "Deployment"
supplemental: true
image:
arm64: "ghcr.io/myorg/dev-image:arm64"
amd64: "ghcr.io/myorg/dev-image:amd64"
workdir: "/app"
sync:
- "./src:/app/src"
- "./config:/app/config"
forward:
- "8080:8080"
ignore:
- "node_modules"
- ".git"
- "dist"
- "coverage"
cache:
- "/app/node_modules"
- "/root/.cache"
env:
- name: "NODE_ENV"
value: "development"
- name: "AWS_ACCESS_KEY_ID"
from_env: true
- name: "AWS_SECRET_ACCESS_KEY"
from_env: true