Tilt for Local Kubernetes Development
Tilt is a developer tool that makes local Kubernetes development fast and intuitive by providing live updates, a visual dashboard, and intelligent resource dependency management. This guide covers installing Tilt on Linux, writing Tiltfiles, configuring live updates for multi-service applications, and debugging running containers.
Prerequisites
- Docker or containerd installed and running
- A local Kubernetes cluster (kind, k3d, Minikube, or Docker Desktop)
kubectlconfigured and working- Basic knowledge of Kubernetes manifests or Helm charts
Install Tilt
# Linux (official install script)
curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash
# Verify installation
tilt version
# macOS
brew install tilt
# From binary (manual)
VERSION=0.33.6
curl -Lo tilt.tar.gz \
"https://github.com/tilt-dev/tilt/releases/download/v${VERSION}/tilt.${VERSION}.linux.x86_64.tar.gz"
tar xzf tilt.tar.gz
sudo mv tilt /usr/local/bin/
Write Your First Tiltfile
The Tiltfile is written in Starlark (a Python dialect) and lives in your project root:
# Tiltfile
# Build the Docker image
docker_build('my-org/web-app', '.')
# Apply Kubernetes manifests
k8s_yaml('k8s/deployment.yaml')
k8s_yaml('k8s/service.yaml')
# Configure the resource for the Tilt dashboard
k8s_resource(
'web-app',
port_forwards='8080:8080', # Local port -> container port
labels=['frontend']
)
# Start Tilt - it opens the dashboard at http://localhost:10350
tilt up
# Run headless (no browser auto-open)
tilt up --hud=false
# Stop and clean up all resources
tilt down
Live Update Configuration
Live update syncs file changes into running containers without a full rebuild:
# Tiltfile with live update
# Define live update steps
live_update_steps = [
# Sync source files into the container
sync('./src', '/app/src'),
# Run a command after sync (e.g., restart the dev server)
run('cd /app && npm run build:watch', trigger=['./src/**/*.ts']),
# Or restart the process
restart_container(),
]
docker_build(
'my-org/web-app',
'.',
dockerfile='Dockerfile.dev',
live_update=live_update_steps
)
Live update for a Python Flask app:
# Sync Python files and restart Gunicorn
docker_build(
'my-org/flask-app',
'.',
live_update=[
sync('./app', '/usr/src/app'),
run('pip install -r requirements.txt',
trigger=['./requirements.txt']),
restart_container(),
]
)
Live update for a Go service:
# Sync and trigger a rebuild inside the container
docker_build(
'my-org/go-service',
'.',
live_update=[
sync('.', '/go/src/my-service'),
run('cd /go/src/my-service && go build -o /usr/local/bin/server .'),
restart_container(),
]
)
Multi-Service Development
Tilt excels at managing multiple services simultaneously:
# Tiltfile for a microservices application
# Load helper functions
load('ext://helm_resource', 'helm_resource', 'helm_repo')
# Add a Helm repo dependency (e.g., PostgreSQL)
helm_repo('bitnami', 'https://charts.bitnami.com/bitnami')
helm_resource(
'postgres',
'bitnami/postgresql',
flags=['--set', 'auth.postgresPassword=devpassword',
'--set', 'primary.persistence.enabled=false'],
labels=['infrastructure']
)
# Build and deploy the API service
docker_build(
'my-org/api-service',
'./services/api',
live_update=[
sync('./services/api/src', '/app/src'),
restart_container(),
]
)
k8s_yaml('./k8s/api-service.yaml')
k8s_resource(
'api-service',
port_forwards='3000:3000',
resource_deps=['postgres'], # Wait for postgres before starting
labels=['backend']
)
# Build and deploy the frontend
docker_build(
'my-org/frontend',
'./services/frontend',
live_update=[
sync('./services/frontend/src', '/app/src'),
run('cd /app && npm run build', trigger=['./services/frontend/src']),
]
)
k8s_yaml('./k8s/frontend.yaml')
k8s_resource(
'frontend',
port_forwards='8080:80',
resource_deps=['api-service'],
labels=['frontend']
)
# Build and deploy the worker
docker_build('my-org/worker', './services/worker')
k8s_yaml('./k8s/worker.yaml')
k8s_resource(
'worker',
resource_deps=['postgres'],
labels=['backend']
)
Resource Dependencies and Ordering
# Ensure correct startup order
# Infrastructure comes first
k8s_resource('redis', labels=['infra'])
k8s_resource('postgres', labels=['infra'])
# Services depend on infrastructure
k8s_resource(
'api',
resource_deps=['redis', 'postgres'],
labels=['services']
)
# Frontend depends on API being ready
k8s_resource(
'frontend',
resource_deps=['api'],
labels=['services']
)
# One-time setup job runs after database is ready
k8s_resource(
'db-migrate',
resource_deps=['postgres'],
labels=['setup'],
pod_readiness='ignore' # Don't wait for pod completion to mark ready
)
Local Resources and Scripts
Tilt can run local scripts as part of the dev loop:
# Run a local command as a Tilt resource
local_resource(
'generate-proto',
cmd='protoc --go_out=. proto/*.proto',
deps=['proto/'],
labels=['codegen']
)
# Run database migrations locally
local_resource(
'db-migrate',
cmd='DATABASE_URL=postgres://localhost:5432/dev ./scripts/migrate.sh',
resource_deps=['postgres'],
labels=['setup']
)
# Watch a config file and apply it
local_resource(
'apply-configmap',
cmd='kubectl apply -f k8s/configmap.yaml',
deps=['k8s/configmap.yaml'],
)
Debugging Workflows
# Enable remote debugging by exposing debug ports
k8s_resource(
'api-service',
port_forwards=[
'3000:3000', # Application port
'9229:9229', # Node.js debug port
]
)
# Open a shell in a running container from Tilt
# Use the Tilt dashboard > resource > Terminal button
# Or manually:
kubectl exec -it $(kubectl get pod -l app=api-service -o jsonpath='{.items[0].metadata.name}') -- bash
# Stream logs for a specific service
kubectl logs -l app=api-service -f --tail=100
# Check Tilt's understanding of your resources
tilt get uiresource
# See what Tilt is building
tilt ci # Run in CI mode (exits after first build completes)
Triggering manual builds:
# Force rebuild a specific resource from CLI
tilt trigger api-service
# From the Tilt dashboard, click the refresh icon on any resource
Using Tilt with kind (local cluster):
# Create a kind cluster with a local registry
cat > kind-config.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
endpoint = ["http://kind-registry:5000"]
EOF
kind create cluster --config kind-config.yaml
# In your Tiltfile, configure for kind's local registry
default_registry('localhost:5000')
Troubleshooting
"No Tiltfile found" error:
# Tiltfile must be in the current directory or specified explicitly
tilt up --file /path/to/Tiltfile
# Verify the file exists and is named exactly "Tiltfile" (capital T)
ls -la Tiltfile
Live update not triggering:
# Check that the sync paths match your actual file locations
# In Tiltfile: sync('./src', '/app/src') means:
# - Local path: ./src (relative to Tiltfile)
# - Container path: /app/src
# Verify with tilt logs
tilt logs api-service
Container restarts in a loop:
# View pod events
kubectl describe pod -l app=api-service
# Check init containers
kubectl get pod -l app=api-service -o yaml | grep -A 10 "initContainers"
Dashboard not accessible:
# Default dashboard URL
open http://localhost:10350
# If port is in use, specify another
tilt up --port 10351
Conclusion
Tilt dramatically accelerates local Kubernetes development by combining intelligent file syncing, automatic rebuilds, and a visual dashboard into a single tool. The Tiltfile's Starlark language is expressive enough to model complex multi-service dependencies while keeping the configuration readable. For teams building microservices, Tilt provides the fast feedback loop that makes Kubernetes development feel as responsive as local development.


