Every code change in the local cluster costs 30–60 seconds — docker build, push or load, kubectl rollout, wait for the new pod — and developers start batching changes or moving back to running services outside the cluster, which defeats the point of having one. Or Tilt is configured, but every save still triggers a full image build because live_update never matches. This page configures Tilt so source changes are synced into running containers in about two seconds, while dependency changes still trigger proper rebuilds. It is part of local Kubernetes development.

Tilt reads a Tiltfile written in Starlark (a Python dialect), watches the files each image depends on, and decides per change whether to rebuild the image or apply a live_update — a set of file syncs and commands run inside the existing container.

Diagnostic

Check how Tilt handled the most recent change and how long it took. The CLI exposes the same data as the web UI:

#!/usr/bin/env bash
set -euo pipefail
touch api/src/routes/health.ts
sleep 8
tilt get uiresource api -o json | jq -r '.status.buildHistory[0] | "\(.startTime)  \(.finishTime)  error=\(.error // "none")"'
tilt logs api --since 30s | grep -E 'Building|Step|live update|Will not perform' | head -8

Expected bad output when live update is not taking effect:

2026-09-18T10:02:11Z  2026-09-18T10:02:49Z  error=none
     Building Dockerfile for platform linux/arm64:
     Step 1 - 4.61s (Building Dockerfile)
     Step 2 - 21.30s (Pushing localhost:5001/api:tilt-9c1f)
     Will not perform Live Update because: Found file(s) not matching any sync (files: [api/src/routes/health.ts])

A 38-second full rebuild, and Tilt names the reason: the changed file did not match any sync path.

Where the 38-Second Rebuild Went Bar chart splitting a full Tilt rebuild into build, push and rollout time. Where the 38-Second Rebuild Went image build 4.6 s push to registry 21.3 s rollout and ready 12.1 s live update total 1.8 s
Live update removes all three bars for source-only changes.

Root cause

live_update only runs when every changed file matches a sync source path in that image's live_update steps. The paths are resolved relative to the Tiltfile, and the most common mistakes are mismatched roots (sync('./src', ...) when the code lives in ./api/src), a docker build context that includes files the sync rules do not cover (tests, config files next to the source), and a container path that does not match where the Dockerfile copies the code. When any changed file falls outside the rules, Tilt falls back to a full rebuild — correctly, because it cannot know how to apply that file. The second failure mode is the opposite: files synced into the container that need a rebuild to take effect, such as a new dependency in package.json, which leaves the container running with stale node_modules.

Push time dominates in the diagnostic because the image is pushed to a registry each time. For source-only changes that cost disappears with live update, which is why getting the sync rules right matters more than any registry tuning.

Resolution

  1. Align sync paths with the Dockerfile's COPY layout. If the Dockerfile does COPY src /app/src, the sync must map the same directory:
docker_build(
    'localhost:5001/api',
    context='./api',
    dockerfile='./api/Dockerfile',
    ignore=['./api/test', './api/**/*.md'],
    live_update=[
        fall_back_on(['./api/package.json', './api/package-lock.json', './api/Dockerfile']),
        sync('./api/src', '/app/src'),
        sync('./api/config', '/app/config'),
        run('kill -USR2 1', trigger=['./api/config']),
    ],
)

ignore removes files from the build context and from watching, so edits to tests no longer force rebuilds of the service image. fall_back_on lists files that must always trigger a full rebuild.

  1. Run the process under a watcher inside the container so synced files are picked up. For Node, node --watch or nodemon restarts on change; many frameworks already reload in development mode:
FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY src ./src
COPY config ./config
CMD ["node", "--watch", "src/server.js"]
  1. For compiled languages, compile on the host and sync the binary. Syncing Go source into a container that would then need a compiler defeats the purpose. Build with local_resource, then sync the output and restart:
local_resource(
    'worker-compile',
    'CGO_ENABLED=0 GOOS=linux go build -o ./worker/build/worker ./worker/cmd/worker',
    deps=['./worker/cmd', './worker/internal'],
    labels=['backend'],
)
docker_build(
    'localhost:5001/worker',
    context='./worker',
    dockerfile='./worker/Dockerfile.dev',
    only=['./build'],
    live_update=[sync('./worker/build/worker', '/app/worker'), run('kill -HUP 1')],
)

Set GOARCH to match the cluster's node architecture when it differs from the host.

  1. Group resources and expose ports so the UI is usable:
k8s_yaml(helm('./charts/shop', values=['./charts/shop/values-local.yaml']))
k8s_resource('api', port_forwards='8080:8080', labels=['backend'], resource_deps=['postgres'])
k8s_resource('worker', labels=['backend'], resource_deps=['worker-compile'])
k8s_resource('postgres', labels=['infra'])
Will Tilt Live-Update This Change? Decision diagram showing how Tilt treats a change depending on which sync and fall_back_on rules match. Will Tilt Live-Update This Change? Which rules match the changed files? all match a sync live update in seconds one matches fall_back_on full rebuild any unmatched file full rebuild with reason
Every changed file must match a sync rule, and none may match fall_back_on.

Expected output

$ touch api/src/routes/health.ts && sleep 4 && tilt logs api --since 10s | grep -iE 'live update|synced'
     Will copy 1 file(s) to container: [api-7c9d5f8b6d-x2kqp/api]
     [Live Update] Syncing api/src/routes/health.ts → /app/src/routes/health.ts
     [Live Update] Done in 1.6s
api  │ Restarting 'src/server.js'

The file is copied into the running container in under two seconds and Node's watcher restarts the process. Changing package.json still triggers a full rebuild, as intended.

Prevention

  1. Keep the Tiltfile next to the Dockerfiles it mirrors and review both together; a COPY path change in a Dockerfile without the matching sync change is the usual regression.

  2. Check for drift before debugging. When behaviour differs from CI, compare the running container's files with the working tree: kubectl exec deploy/api -- sh -c 'cd /app && find src -type f -newer package.json | head' shows synced files, and tilt trigger api forces a clean rebuild to rule out live-update drift.

  3. Run tilt ci in CI for the same Tiltfile. It brings up every resource, waits for them to be healthy and exits, which catches broken Tiltfiles the day they break rather than on the next developer's machine.

Live Update Cycle for a Source Change Ordered steps from saving a file to the process restarting inside the container. Live Update Cycle for a Source Change 1 — file saved on host 2 — Tilt matches a sync rule 3 — file copied into container 4 — optional run step executes 5 — process watcher restarts app about 2 s
No image build, push or pod replacement happens for a source-only change.

Platform caveats

Apple Silicon (ARM64): Tilt builds for the cluster's architecture automatically when using kind on the same machine. For local_resource builds of compiled binaries, set GOARCH=arm64 explicitly, or the synced binary fails with exec format error.

macOS (Docker Desktop): Tilt's file watching runs on the host, so there are no bind-mount inotify issues; syncing uses kubectl cp-like tar streams that are unaffected by virtiofs performance.

WSL2: keep the repository inside the WSL filesystem, not under /mnt/c. Tilt's watcher does not receive file events reliably from Windows-mounted drives, which makes live update appear to do nothing.

Rollback

Remove the live_update argument to return to full rebuilds on every change; nothing else depends on it:

#!/usr/bin/env bash
set -euo pipefail
git restore Tiltfile
tilt down
tilt up

Frequently Asked Questions

Why does Tilt say "Found file(s) not matching any sync"?

At least one changed file is outside every sync source path in that image's live_update. Add a sync for its directory, or exclude it from the build context with ignore or only if the image does not need it.

Is live update safe to use for everything?

It is safe for development, but the container gradually diverges from what its image would contain. Rebuild from scratch when switching branches or when something works locally but fails in CI.

How does Tilt compare with Skaffold's file sync?

Both sync files into running containers. Tilt's rules are expressed in Starlark with explicit fallbacks and triggers, and its UI shows why each rebuild happened. Skaffold is declarative YAML and integrates closely with its CI deploy pipeline. Pick one and use it consistently.

Do I still need a registry with Tilt?

For kind, Tilt can load images directly without one, but it detects a local registry through the local-registry-hosting ConfigMap and pushes there instead, which is faster for large images and multi-node clusters.