What's Actually Happening
Tilt's live update feature is not updating running containers when files change. Code changes are not reflected in the running application.
The Error You'll See
```bash $ tilt up
# File changes detected but not applied: [demo] Build Failed: failed to sync files ```
Sync error:
Error: no sync or run_in_container specified for live_updateContainer error:
Error: container not found: appPermission error:
Error: permission denied: /app/srcWhy This Happens
- 1.Live_update not configured - Missing live_update section in Tiltfile
- 2.Sync paths wrong - Source paths don't match container paths
- 3.Container name wrong - fall_back_on or container specified incorrectly
- 4.Dockerfile incompatible - Dockerfile doesn't support file updates
- 5.Permission issues - Container user can't write to synced directories
- 6.Process not reloading - Application needs restart after file changes
Step 1: Check Tiltfile Configuration
```python # View Tiltfile: cat Tiltfile
# Check live_update configuration: cat Tiltfile | grep -A10 "live_update"
# Basic live_update syntax: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), run('cd /app && npm install', trigger='./package.json'), ] )
# Check if docker_build has live_update: docker_build( 'my-image', '.', # Missing live_update = full rebuild only )
# Add live_update: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), ] )
# Validate Tiltfile: tilt ci
# Check Tiltfile syntax: tilt dump > tilt-config.json cat tilt-config.json | jq '.' ```
Step 2: Verify Sync Paths
```python # Check sync paths: # Format: sync('local_path:container_path')
# Verify local path exists: ls ./src
# Check container path in Dockerfile: cat Dockerfile | grep WORKDIR cat Dockerfile | grep COPY
# Common sync configurations: # For Node.js: sync('./src:/app/src')
# For Python: sync('./app:/app/app')
# For Go: sync('./cmd:/app/cmd') sync('./pkg:/app/pkg')
# Multiple syncs: live_update=[ sync('./src:/app/src'), sync('./public:/app/public'), ]
# Check sync in Tilt UI: # Open Tilt UI -> Click resource -> View "Build History" # Look for "Live Update" in build log
# Test sync manually: docker exec -it <container> ls /app/src ```
Step 3: Add run Commands
```python # Add run_in_container for updates: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), run('pip install -r requirements.txt', trigger='./requirements.txt'), ] )
# Run on container: # run() - runs in container after sync # run_local() - runs on host machine
# Restart application: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), run('pkill -f "python" || true'), # Kill process run('python /app/main.py &'), # Restart ] )
# Use trigger for selective updates: live_update=[ sync('./src:/app/src'), run('npm install', trigger='./package.json'), # Only on package.json change run('npm run build', trigger='./src/**/*.ts'), # Only on TS file changes ]
# Fall back on full rebuild: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), ], match_in_env_vars=True, # Check env vars for image name ) ```
Step 4: Fix Container Permissions
```bash # Check container user: docker exec -it <container> whoami
# Check file permissions in container: docker exec -it <container> ls -la /app
# If permission denied, fix in Dockerfile: # Option 1: Run as root USER root
# Option 2: Fix directory permissions RUN chown -R app:app /app
# Option 3: Use chmod RUN chmod -R 755 /app
# Check if container has write access: docker exec -it <container> touch /app/src/test.txt
# Fix with run command: live_update=[ sync('./src:/app/src'), run('chown -R app:app /app/src || true'), ]
# Check container user in Dockerfile: cat Dockerfile | grep USER ```
Step 5: Configure Fallback Strategy
```python # Set fallback_on for rebuild triggers: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), ], fallback_on=[ './requirements.txt', # Full rebuild on requirements change './Dockerfile', # Full rebuild on Dockerfile change ] )
# Only build on changes: docker_build( 'my-image', '.', only=['./src', './package.json'], # Only watch these paths live_update=[ sync('./src:/app/src'), ] )
# Ignore files: docker_build( 'my-image', '.', ignore=['./node_modules', './.git'], live_update=[ sync('./src:/app/src'), ] )
# Set container target for live_update: docker_build( 'my-image', '.', live_update=[ sync('./src:/app/src'), ], target='development', # Build this Dockerfile stage ) ```
Step 6: Use k8s_yaml and k8s_resource
```python # Check resource configuration: cat Tiltfile | grep -A5 "k8s_resource"
# Associate image with resource: docker_build('my-image', '.', live_update=[sync('./src:/app/src')])
k8s_yaml('kubernetes.yaml')
k8s_resource( 'my-app', port_forwards=8080, extra_pod_selectors=[{'app': 'my-app'}] )
# Set resource name: k8s_resource( 'my-app', new_name='demo', # Rename in Tilt UI port_forwards=8080, )
# Multiple resources: k8s_resource('app1', port_forwards=8080) k8s_resource('app2', port_forwards=8081)
# Check resource pod: tilt get pods
# View resource status: tilt get resources ```
Step 7: Debug Live Update
```bash # Enable verbose logging: tilt up --verbose
# Check Tilt logs: tilt logs
# View Tilt UI: # Open http://localhost:10350
# Check file watcher: # In Tiltfile, add: watch_file('./config.yaml')
# Trigger manual rebuild: tilt trigger <resource-name>
# View build history: # In Tilt UI -> Resource -> Build History
# Check for file sync errors: tilt logs | grep -i "sync|update|error"
# Test container update manually: docker cp ./src/file.py <container>:/app/src/file.py docker exec <container> cat /app/src/file.py ```
Step 8: Fix Process Reload
```python # Applications may need restart after file sync:
# Python with gunicorn: live_update=[ sync('./src:/app/src'), run('kill -HUP 1'), # Signal gunicorn to reload ]
# Node.js with nodemon: # Use nodemon in Dockerfile: CMD ["nodemon", "index.js"] # Files sync, nodemon auto-reloads
# Go with CompileDaemon: # In Dockerfile: CMD ["CompileDaemon", "-build=go build -o app .", "-command=./app"] # Or use air for hot reload: CMD ["air"]
# Java with Spring Boot DevTools: # Add dependency and: live_update=[ sync('./src:/app/src'), run('./mvnw compile'), ]
# Generic restart: live_update=[ sync('./src:/app/src'), run('pkill -f "main.py" || true'), run('python /app/main.py &'), ] ```
Step 9: Check Dockerfile Compatibility
```dockerfile # Check Dockerfile for live_update compatibility:
# Bad: Entrypoint blocks sync ENTRYPOINT ["./start.sh"]
# Good: CMD can be overridden CMD ["python", "main.py"]
# Bad: Single layer COPY . /app RUN pip install -r requirements.txt && pip cache purge
# Good: Separate layers COPY requirements.txt /app/ RUN pip install -r requirements.txt COPY . /app
# Use multi-stage for smaller updates: FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build
FROM node:18 AS runtime WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/main.js"]
# Target development stage: docker_build( 'my-image', '.', target='development', # Use development stage live_update=[ sync('./src:/app/src'), ] ) ```
Step 10: Tilt Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-tilt-live-update.sh #!/bin/bash
echo "=== Tiltfile Content ===" cat Tiltfile
echo "" echo "=== Docker Build Config ===" grep -A20 "docker_build" Tiltfile
echo "" echo "=== Live Update Config ===" grep -A10 "live_update" Tiltfile
echo "" echo "=== Dockerfile WORKDIR ===" grep -i "WORKDIR|COPY|CMD" Dockerfile
echo "" echo "=== Current Directory ===" ls -la
echo "" echo "=== Source Files ===" ls -la ./src 2>/dev/null || ls -la ./app 2>/dev/null || echo "No src or app directory"
echo "" echo "=== Tilt Status ===" tilt get resources 2>/dev/null || echo "Tilt not running"
echo "" echo "=== Docker Containers ===" docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
echo "" echo "=== Test File Sync ===" touch ./src/.tilt-test-$(date +%s) echo "Created test file, check Tilt UI for sync"
echo "" echo "=== Recommendations ===" echo "1. Add live_update to docker_build in Tiltfile" echo "2. Verify sync paths match container paths" echo "3. Check container has write permissions" echo "4. Add run commands to restart application" echo "5. Use fallback_on for dependency changes" echo "6. Ensure Dockerfile supports file updates" echo "7. Check application reload mechanism" EOF
chmod +x /usr/local/bin/check-tilt-live-update.sh
# Usage: /usr/local/bin/check-tilt-live-update.sh ```
Tilt Live Update Checklist
| Check | Expected |
|---|---|
| live_update defined | In docker_build |
| Sync paths correct | Local:Container match |
| Container permissions | Write access to sync dir |
| Process reload | App restarts on change |
| Fallback configured | Full rebuild on key changes |
| Dockerfile compatible | CMD not blocking |
| Files monitored | Tilt sees file changes |
Verify the Fix
```bash # After fixing Tilt live update
# 1. Start Tilt tilt up // Tilt UI opens
# 2. Make file change echo "// test" >> src/main.py // Tilt shows live update
# 3. Check container docker exec -it <container> cat /app/src/main.py // Change reflected
# 4. View build history # In Tilt UI -> Resource -> Build History // Live Update appears
# 5. Test multiple changes # Make several file changes // Updates applied incrementally
# 6. Check application curl http://localhost:8080 // Response reflects changes ```
Related Issues
- [Fix Docker Compose Up Failed](/articles/fix-docker-compose-up-failed)
- [Fix Skaffold Build Failed](/articles/fix-skaffold-build-failed)
- [Fix Kubernetes Deployment Not Working](/articles/fix-kubernetes-deployment-not-working)