Faster Development with Live Update (Tutorial)
This tutorial looks at a Tiltfile
with build optimizations.
We explain what they do, and why you would want to use them.
(This is a tutorial that walks you through a sample project. If you’re looking for technical specs and details, check out the Live Update Reference docs.)
In the Tutorial, we introduced the docker_build()
function.
This function builds a Docker image. Tilt will watch the inputs to the
image, and do a fresh build every time the inputs change.
However, rebuilding your Docker image every time you change some code isn’t super efficient, even if your caching is good. The efficiency is even worse if you’re working with compiled languages, and even worse if you’re pushing images into the cloud.
That’s why Tilt has a feature called Live Update, to make your containerized development lightning-fast.
Let’s Look At an Example
Let’s look at an example app, random_number
:
git clone https://github.com/tilt-dev/random_number
cd random_number
This “microservice” app consists of:
numbers
, a Python+Flask server which serves a random numberfe
, a Golang server frontend which hitsnumbers
for a random number
The Tiltfile
at the root of the repo contains this example:
# Service: numbers
docker_build('random_number/numbers', 'numbers',
live_update=[
sync('./numbers', '/app'),
run('cd /app && pip install -r requirements.txt',
trigger='numbers/requirements.txt'),
]
)
# Service: fe
docker_build_with_restart('random_number/fe', 'fe',
entrypoint='/go/bin/fe',
live_update=[
sync('./fe', '/go/src/github.com/tilt-dev/random_number/fe'),
run('go install github.com/tilt-dev/random_number/fe'),
]
)
This looks similar to the Tiltfile
in previous tutorials, but when we specify
how to build the image with docker_build()
, we pass an additional argument,
live_update
, containing a list of steps for how to update the running container.
Service #1: numbers
(Python + Flask)
Let’s zoom in on the live_update
configuration for the numbers
service first:
docker_build('random_number/numbers', 'numbers',
live_update=[
sync('./numbers', '/app'),
# run `pip install` IF `requirements.txt` has changed
run('cd /app && pip install -r requirements.txt',
trigger='numbers/requirements.txt'),
]
)
These lines configure Tilt to, when possible, update containers running the image random_number/numbers
incrementally rather than doing a full build every time code changes. We’ll step through it line by line.
sync('./numbers', '/app'),
The sync
method watches a file or directory on your local filesystem, and when it detects a change, copies the changed file(s) into your container at the specified path.
In this case, we map the directory ./numbers
(relative to the Tiltfile) into
the container filesystem at /app
.
The normal docker_build
behavior is to watch all files in the Docker build context (here, ./numbers
),
and any time one changes, rebuild the image and re-deploy it to Kubernetes. The sync
here says that, if
the changed file matches ./numbers
, to instead do a live_update
: Tilt will copy
the changed files into the container and execute any appropriate run
steps, without actually building or pushing a Docker image or performing a Kubernetes deploy.
run('cd /app && pip install -r requirements.txt', trigger='numbers/requirements.txt'),
The run
method runs shell commands inside your container.
This particular run step relies on a trigger
: when this resource Live Updates, Tilt will
run this command in the container if any changed files match the trigger (here,
numbers/requirements.txt
). This specificity means that we’ll only run pip install
if
dependencies have actually changed.
Service #2: fe
(Golang)
Let’s now look at the live_update
configuration for the other service in this app, the frontend, which is written in Go:
docker_build_with_restart('random_number/fe', 'fe',
# command to run on container start/re-run on live update
entrypoint='/go/bin/fe',
live_update=[
sync('./fe', '/go/src/github.com/tilt-dev/random_number/fe'),
run('go install github.com/tilt-dev/random_number/fe'),
]
)
The eagle-eyed among you will notice that the function called here is slightly different: instead of docker_build
, we call docker_build_with_restart
. More on that in a moment; first let’s go through the live_update
steps one a time.
sync('./fe', '/go/src/github.com/tilt-dev/random_number/fe')
As we saw in the configuration for numbers
, this sync
call maps a local directory to a path on the container. Any time a file on disk changes, if it matches the path ./fe
, Tilt will copy it to the designated path in the container.
run('go install github.com/tilt-dev/random_number/fe')
Unlike the run
step seen in numbers
, this one lacks a trigger
, which means that Tilt will run this command in the container every time a Live Update happens (i.e., when a file matching a sync
changes). In this example, every time Tilt syncs changed files to the container, it then recompiles the Go binary.
One of the major build optimizations here is that Tilt runs your command inside the existing container, instead of building from scratch to create a fresh image. This is much closer to how we normally run commands for local development. Real humans don’t delete all their code and re-clone it from GitHub every time we need to do a new build! Instead, we re-run the command in the same directory. Modern tools then take advantage of local caches; Tilt runs commands with the same approach, but inside a container.
- What about
docker_build_with_restart
?
This function is imported from
the restart_process
extension
via a load
call at the top of the Tiltfile:
load('ext://restart_process', 'docker_build_with_restart')
(Extensions are open-source packaged functions that extend the capability Tilt; check out the docs for more info.)
docker_build_with_restart
works just like the docker_build
call you’re used to, except that at the end of every Live Update, it restarts your process–in this case, it runs the newly built Go
binary. Notice the extra argument entrypoint
(in this example, /go/bin/fe
): this is the command
that you want to run on container start and re-run on Live Update.
For languages/frameworks like Node or Flask that have hot reloading (i.e., they can pick up code
changes without restarting the process), you don’t need to restart your process. For instance, the
numbers
app takes advantage of Flask’s hot reloading, and thus doesn’t need a docker_build_with_restart
call.
Further Reading
In this guide, we explored just a few of the functions we can use in a Tiltfile
to keep your build fast. For even more functions and tricks,
read the complete Tiltfile API reference.
For more details on Live Update, see the Live Update Reference Documentation.
If you need more specifics on how to set up Live Update with your programming language of choice, all our major example projects use Live Update: