Core Concepts
4 min readRapid overview
- Tiltfile & Tilt for Local Kubernetes Development
- What is Tilt?
- Core Concepts
- Basic Tiltfile Structure
- Key Functions
- Docker Build Strategies
- Basic Build
- Live Update (Hot Reload)
- Live Update Strategies
- Docker Build with Restart
- Kubernetes Configuration
- Loading Manifests
- Resource Configuration
- Resource Dependencies
- Local Resources
- Running Tests
- Build Steps
- Helm Integration
- Basic Helm Deploy
- Helm with Image Override
- Extensions
- Loading Extensions
- Common Extensions
- Multi-Service Development
- Microservices Setup
- Shared Libraries
- Advanced Patterns
- Conditional Configuration
- Custom Buttons
- Resource Groups
- Performance Optimization
- Ignore Unnecessary Files
- Only Include Needed Files
- Parallel Builds
- Debugging
- Tilt Commands
- Debugging Tiltfile
- Interview Questions
- 1. What problem does Tilt solve?
- 2. Explain Live Update vs Full Rebuild
- 3. How do resource dependencies work?
- 4. Best practices for Tiltfile organization?
Tiltfile & Tilt for Local Kubernetes Development
What is Tilt?
Tilt is a local Kubernetes development tool that watches your files, rebuilds containers, and updates your cluster in real-time. It uses a Tiltfile (written in Starlark, a Python dialect) to define your development environment.
Core Concepts
Basic Tiltfile Structure
# Tiltfile
# Load extensions
load('ext://restart_process', 'docker_build_with_restart')
# Define Kubernetes resources
k8s_yaml('kubernetes/deployment.yaml')
# Build Docker image and deploy
docker_build('myapp', '.')
# Configure resource behavior
k8s_resource('myapp', port_forwards='8080:80')
Key Functions
| Function | Purpose |
|---|---|
docker_build() | Build Docker image from Dockerfile |
k8s_yaml() | Apply Kubernetes manifests |
k8s_resource() | Configure a Kubernetes resource |
local_resource() | Run local commands (tests, builds) |
helm() | Deploy Helm charts |
Docker Build Strategies
Basic Build
docker_build(
'api-server', # Image name
'.', # Build context
dockerfile='Dockerfile',
build_args={'VERSION': '1.0.0'},
ignore=['./tests', './docs'],
)
Live Update (Hot Reload)
Live Update syncs file changes without full image rebuilds.
docker_build(
'frontend',
'.',
live_update=[
# Sync local changes to container
sync('./src', '/app/src'),
sync('./public', '/app/public'),
# Run command after sync
run('npm install', trigger=['./package.json']),
# Restart process if needed
run('kill -HUP 1', trigger=['./src/**/*.ts']),
]
)
Live Update Strategies
# Sync files only (for interpreted languages)
live_update=[
sync('./src', '/app/src'),
]
# Sync and run build (for compiled languages)
live_update=[
sync('./src', '/app/src'),
run('go build -o /app/server ./cmd/server'),
]
# Full restart (when config changes)
live_update=[
sync('./config', '/app/config'),
restart_container(),
]
Docker Build with Restart
load('ext://restart_process', 'docker_build_with_restart')
docker_build_with_restart(
'api-server',
'.',
entrypoint='/app/server',
live_update=[
sync('./src', '/app/src'),
run('go build -o /app/server ./cmd/server', trigger=['./src/**/*.go']),
],
)
Kubernetes Configuration
Loading Manifests
# Single file
k8s_yaml('deploy/app.yaml')
# Multiple files
k8s_yaml(['deploy/app.yaml', 'deploy/service.yaml'])
# Glob pattern
k8s_yaml(glob('deploy/*.yaml'))
# Kustomize
k8s_yaml(kustomize('deploy/overlays/dev'))
# Helm
k8s_yaml(helm('charts/myapp', values=['values-dev.yaml']))
Resource Configuration
k8s_resource(
'api-server',
port_forwards=['8080:80', '9090:9090'],
resource_deps=['database', 'redis'],
labels=['backend'],
trigger_mode=TRIGGER_MODE_MANUAL, # Don't auto-update
)
k8s_resource(
'database',
port_forwards='5432:5432',
labels=['infrastructure'],
)
Resource Dependencies
# API depends on database being ready
k8s_resource('api-server', resource_deps=['database'])
# Frontend depends on API
k8s_resource('frontend', resource_deps=['api-server'])
Local Resources
Running Tests
local_resource(
'unit-tests',
cmd='npm test',
deps=['./src', './test'],
labels=['tests'],
auto_init=False, # Don't run on startup
trigger_mode=TRIGGER_MODE_MANUAL,
)
local_resource(
'integration-tests',
cmd='npm run test:integration',
resource_deps=['api-server', 'database'],
labels=['tests'],
)
Build Steps
# Build frontend before Docker build
local_resource(
'frontend-build',
cmd='npm run build',
deps=['./src', './package.json'],
labels=['build'],
)
# Make docker_build depend on local build
docker_build(
'frontend',
'.',
dockerfile='Dockerfile.prod',
only=['./build'],
)
k8s_resource('frontend', resource_deps=['frontend-build'])
Helm Integration
Basic Helm Deploy
k8s_yaml(helm(
'charts/myapp',
name='myapp',
namespace='default',
values=['values.yaml', 'values-dev.yaml'],
set=[
'image.repository=myapp',
'replicaCount=1',
]
))
Helm with Image Override
docker_build('myapp', '.')
k8s_yaml(helm(
'charts/myapp',
set=['image.repository=myapp', 'image.tag=latest']
))
Extensions
Loading Extensions
# From tilt-extensions repo
load('ext://restart_process', 'docker_build_with_restart')
load('ext://namespace', 'namespace_create')
load('ext://secret', 'secret_from_dict')
# Use namespace extension
namespace_create('my-namespace')
# Create secrets
secret_from_dict('db-secret', 'my-namespace', inputs={
'username': 'admin',
'password': os.getenv('DB_PASSWORD'),
})
Common Extensions
| Extension | Purpose |
|---|---|
restart_process | Restart container process without rebuild |
namespace | Create/manage namespaces |
secret | Create Kubernetes secrets |
git_resource | Deploy from Git repos |
helm_remote | Deploy Helm charts from repos |
configmap | Create ConfigMaps from files |
Multi-Service Development
Microservices Setup
# Tiltfile for microservices
# Shared configuration
default_registry('localhost:5000')
# Database (external dependency)
k8s_yaml('infra/postgres.yaml')
k8s_resource('postgres', port_forwards='5432', labels=['infrastructure'])
# Redis
k8s_yaml('infra/redis.yaml')
k8s_resource('redis', port_forwards='6379', labels=['infrastructure'])
# API Gateway
docker_build('api-gateway', './services/gateway')
k8s_yaml('services/gateway/k8s.yaml')
k8s_resource('api-gateway',
port_forwards='8080',
resource_deps=['postgres', 'redis'],
labels=['services'])
# User Service
docker_build('user-service', './services/user',
live_update=[
sync('./services/user/src', '/app/src'),
])
k8s_yaml('services/user/k8s.yaml')
k8s_resource('user-service',
resource_deps=['postgres'],
labels=['services'])
# Order Service
docker_build('order-service', './services/order')
k8s_yaml('services/order/k8s.yaml')
k8s_resource('order-service',
resource_deps=['postgres', 'redis'],
labels=['services'])
Shared Libraries
# Watch shared library and rebuild dependents
watch_file('./shared')
docker_build('service-a', './services/a',
deps=['./shared'])
docker_build('service-b', './services/b',
deps=['./shared'])
Advanced Patterns
Conditional Configuration
# Development vs staging
config.define_string('environment', args=True)
cfg = config.parse()
env = cfg.get('environment', 'dev')
if env == 'dev':
k8s_yaml(kustomize('deploy/overlays/dev'))
allow_k8s_contexts('docker-desktop')
else:
k8s_yaml(kustomize('deploy/overlays/staging'))
allow_k8s_contexts('staging-cluster')
Custom Buttons
k8s_resource('api-server',
port_forwards='8080',
links=[
link('http://localhost:8080/swagger', 'Swagger UI'),
link('http://localhost:8080/health', 'Health Check'),
],
)
# Custom action button
local_resource(
'seed-database',
cmd='./scripts/seed.sh',
auto_init=False,
trigger_mode=TRIGGER_MODE_MANUAL,
)
Resource Groups
# Use labels to organize in UI
k8s_resource('frontend', labels=['frontend'])
k8s_resource('api-server', labels=['backend'])
k8s_resource('worker', labels=['backend'])
k8s_resource('postgres', labels=['database'])
k8s_resource('redis', labels=['cache'])
Performance Optimization
Ignore Unnecessary Files
docker_build('myapp', '.',
ignore=[
'./node_modules',
'./.git',
'./tests',
'./docs',
'**/*.md',
'**/*.test.ts',
])
Only Include Needed Files
docker_build('frontend', '.',
only=[
'./build',
'./package.json',
])
Parallel Builds
# Tilt runs builds in parallel by default
# Use resource_deps to control order when needed
docker_build('service-a', './a') # Parallel
docker_build('service-b', './b') # Parallel
docker_build('service-c', './c') # Parallel
# Only service-d waits
docker_build('service-d', './d')
k8s_resource('service-d', resource_deps=['service-a', 'service-b'])
Debugging
Tilt Commands
# Start Tilt
tilt up
# Start with specific resources
tilt up frontend api-server
# Start in CI mode (exit on completion)
tilt ci
# View Tilt UI
tilt up # Opens at http://localhost:10350
# Get resource status
tilt get
tilt describe <resource>
# Trigger manual rebuild
tilt trigger <resource>
# View logs
tilt logs <resource>
Debugging Tiltfile
# Print debug info
print("Building with config:", cfg)
# Fail with message
fail("Missing required environment variable")
# Local commands for debugging
local('kubectl get pods')
Interview Questions
1. What problem does Tilt solve?
Tilt eliminates the slow inner development loop of:
- Make code change
- Build Docker image
- Push to registry
- Update Kubernetes deployment
- Wait for pod rollout
- Test change
With Tilt's live update, changes sync in seconds.
2. Explain Live Update vs Full Rebuild
Live Update:
- Syncs changed files directly to running container
- No image rebuild needed
- Sub-second updates
- Best for interpreted languages (Python, Node.js)
Full Rebuild:
- Complete Docker build cycle
- Required when dependencies change
- Slower but ensures clean state
3. How do resource dependencies work?
resource_deps ensures resources start in order:
- Dependent resource waits for dependencies to be "ready"
- Ready = Kubernetes reports resource as available
- Prevents startup race conditions
4. Best practices for Tiltfile organization?
- Group related resources with labels
- Use extensions for common patterns
- Configure
ignoreto skip unnecessary rebuilds - Use
live_updatefor fast iteration - Set appropriate
trigger_modefor expensive operations - Document custom configurations with comments