Skip to main content
Version: 1.14.1

Inner-Loop Development With App Watch

Experimental

app watch is experimental: it has been validate on a limited set of frameworks and builder iamges. Behavior on other app types and binary layouts is not yet guaranteed.

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 (every run)

Each time you start app watch, Epinio patches the app's existing source blob, runs buildpack staging, and patches the running deployment to inject a supervisor wrapper as PID 1. The supervisor starts your app and relaunches it when sync delivers an updated binary or source files.

The startup push runs on every invocation, not just the first. This is deliberate: a normal epinio app push (or any redeploy) recreates the pod without the supervisor, so re-running app watch always re-establishes it.

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. The supervisor then restarts the app process with the updated files.

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 already exist in Epinio and have been pushed at least once (epinio app push). app watch patches the existing source blob; it cannot bootstrap a brand-new app on its own.
  • Your workstation runs Linux, macOS, or Windows with WSL.

Quick start​

cd my-app/
epinio app push my-app
epinio app watch my-app

Run epinio app push once so the app exists. When you start app watch, Epinio restages the app and installs the supervisor; after that, 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.

After go.mod changes, restart app watch: the startup push re-runs the buildpack and re-resolves dependencies before watching resumes.

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 during a watch session. This file records the file hashes used to detect changes between poll cycles.

The state file is cleared automatically at the start of every app watch run, so each session begins with a fresh startup push that reinstalls the supervisor. You do not need to delete it by hand.

While an app is under watch, its deployment keeps the supervisor wrapper and pinned image. Run a regular epinio app push or epinio app restage when you are done with inner-loop development to restore the standard Helm-managed deployment.

CLI flags​

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

See also​