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)
  • kubectl configured 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.