Skip to main content
Version: Next 🚧

Inner-loop development with app watch

epinio app watch shortens the edit-compile-test loop by syncing local changes directly into a running pod, skipping the full buildpack pipeline on every save.

How it works​

app watch has two phases:

Startup (first run)

On the first run there is no local state file, so app watch performs a full buildpack push to prime the build cache. It then patches the running deployment to inject a supervisor wrapper as PID 1. The supervisor starts your app and watches for an updated binary or source files delivered by the sync phase.

Sync (subsequent runs)

After startup, app watch polls the source directory every 500 ms. When it detects a changed file it enters sync mode:

  • Binary mode (compiled languages): runs your build_cmd, tars the output binary, and uploads it to the running pod via the Epinio API. The pod-side supervisor replaces the running binary and restarts the app in place.
  • Files mode (interpreted languages): tars only the changed files and uploads them into the pod's source directory. If your runtime watches for file changes (Node.js, Python, etc.) it will pick them up automatically.

The mode is chosen by the presence of build_cmd and binary in .epinio-sync.yaml. If neither is set, files mode is used.

Prerequisites​

  • The application namespace is targeted (epinio target <namespace>) or you pass --namespace.
  • The application must be known to Epinio. app watch creates it on the first push if it does not exist yet.
  • Your workstation runs Linux, macOS, or Windows with WSL.

Quick start​

cd my-app/
epinio app watch my-app

On first run this is equivalent to epinio app push. Once the startup push completes, save any source file and the terminal will show sync progress:

Synced in 312ms (via API)

Example configurations​

Go​

Go is a compiled language so binary mode is used. Add .epinio-sync.yaml to your project root:

# .epinio-sync.yaml
build_cmd: "CGO_ENABLED=0 go build -o ./bin/my-app ."
binary: "./bin/my-app"

On every save, app watch runs build_cmd, uploads the new binary into the pod, and the supervisor restarts the app automatically. No extra tooling required.

Delete .epinio-patch-state and re-run app watch after go.mod changes to trigger a fresh buildpack run that re-resolves dependencies.

Node.js​

Node.js is interpreted so no build_cmd or binary is needed. Files mode is the default. On every save, changed files are synced into the pod and the running process is restarted cleanly by the supervisor.

No .epinio-sync.yaml is required for a standard Paketo Node.js app.

If your source layout differs from the Paketo default, add a files_dest override:

# .epinio-sync.yaml
files_dest: "/workspace/source/app"

Python​

Python works the same way as Node.js -- files mode with no build_cmd. On every save, changed .py and template files are synced into the pod and the interpreter restarts fresh.

No .epinio-sync.yaml is required for a standard Paketo Python app.

# .epinio-sync.yaml (only needed for non-standard layouts)
files_dest: "/workspace/source/app"

All configuration options​

Place .epinio-sync.yaml in the root of your source directory to configure binary mode or override defaults. All fields are optional.

FieldDefaultDescription
build_cmd—Shell command to build the binary. When set alongside binary, enables binary mode.
binary—Path to the built binary relative to the source root.
binary_dest/epinio-sync/appDestination path inside the pod for the synced binary. Only change this if the supervisor is configured to match.
files_dest/workspace/source/appDestination directory inside the pod for files mode syncs.
process_cmdauto-discoveredCommand the supervisor runs to start the app on first boot. By default it uses /cnb/process/web when the buildpack defines it, otherwise the image's first CNB process. Override for non-CNB images.

Non-CNB builders​

Cloud Native Buildpacks images are handled automatically. If your builder produces an image without /cnb/process entries, set process_cmd to the command that starts your app, and binary_dest or files_dest to match wherever the builder places the workload:

process_cmd: "/app/bin/start"
binary_dest: "/app/bin/my-app"

Ignoring files​

app watch respects the same ignore rules as epinio push:

FilePurpose
.gitignoreStandard git ignore patterns. Matched files are excluded from sync.
.epinioignoreEpinio-specific ignores. Use this for files you want in git but not in the push (e.g. test fixtures, local config).

Patterns from both files are combined. Either file may be absent.

The watch state file (.epinio-patch-state) and config file (.epinio-sync.yaml) are always excluded automatically.

Performance on large codebases​

To detect changes, app watch hashes every non-ignored file in the source directory on each poll cycle (every 500 ms). On a large codebase this adds noticeable CPU and disk load, and slows down change detection.

Use the ignore files to keep the watched set small. Dependency directories and build output are the usual offenders:

# .epinioignore (or .gitignore)
node_modules/
vendor/
.venv/
dist/
bin/
*.log

Everything excluded here is also excluded from the sync payload, so a tight ignore list makes both change detection and uploads faster.

State file​

app watch writes .epinio-patch-state in the source directory after a successful startup push. This file records the file hashes used to detect changes on the next run.

If this file is present, app watch skips the startup push and goes straight into sync mode. To force a fresh startup push (for example, after a dependency change), delete the state file:

rm .epinio-patch-state
epinio app watch my-app

CLI flags​

FlagDefaultDescription
--namespace, -ntargeted namespaceNamespace the application lives in
--pathcurrent directoryPath to the application source directory

See also​