Live Update Reference
Live Update optimizes your setup to get updates down from minutes to seconds.
This document is a technical specification of live_update
. If you’re looking
for sample projects and examples for your project, see:
Tiltfile API
When specifying how to build an image (via docker_build()
or custom_build()
), you may optionally pass the live_update
argument.
live_update
takes a list of LiveUpdateSteps
that tell Tilt how to update a running container in place (instead of paying the cost of building a new image and redeploying).
The list of LiveUpdateSteps
must be, in order:
- 0 or more
fall_back_on
steps - 0 or more
sync
steps - 0 or more
run
steps
When you tilt up
, your initial build will be a full build—i.e., the specified Docker build or Custom build.1
When a file changes:
- If it matches any of the files in a
fall_back_on
step, we will fall back to a full rebuild + deploy (i.e. the normal, non-live_update process). - Otherwise, if it matches any of the local paths in
sync
steps, a live update will be executed as follows:- copy any changed files according to
sync
steps - for every
run
step:- if the
run
specifies one or moretriggers
, execute the command iff any changed files match the given triggers - otherwise, simply execute the command
- if the
- copy any changed files according to
LiveUpdateSteps
Each of the functions above returns a LiveUpdateStep
– an object like any other, i.e. it can be assigned to a variable, etc. That means that something like this is perfectly valid syntax:
sync_src = sync('./source', '/app/src')
sync_static = sync('./static', '/app/static')
docker_build('my-img', '.', live_update=[sync_src, sync_static])
As part of Tiltfile validation, we check that all of the LiveUpdateSteps
you’ve created have been used in at least one Live Update call. If not, we throw an error.
sync(local_path: str, remote_path: str)
sync
steps are the backbone of a Live Update. (For this reason, we’ll discuss them first, even though they may be preceeded by one or more fall_back_on
steps in a Tiltfile.)
Tilt will only run a Live Update if it detects a change to one or more files matching a sync
step.
A sync
call takes two args: the local path of a file/directory, and the remote path to which Tilt should sync that file/directory if it changes. (This includes deleting the file remotely if it is deleted locally.)
What files can I sync? How are builds triggered?
When you tell Tilt how to build an image, you specify some set of files to watch. In the case of a docker_build
call, Tilt watches the directory that you pass as context
. Your sync’d local paths must fall within that context. (If you’re using custom_build
, all of the above applies, only with deps
in place of context
.) Let’s look at some examples:
The sync
above is invalid, because it attempts to sync files that we’re not even watching (seen here highlighted in blue). To put it another way: there’s no way for those files to get into the container in the first place, because they would never be included in the Docker build.
Above is an example of a valid sync
s. A change to any of the green files will kick off a Live Update, because they match a sync
step. A change to any of the yellow files will kick off a full Docker build + deploy, because they’re part of the Docker context but we don’t have instructions on how to Live Update them. (Want to be more selective in which files do/don’t kick off full builds? Check out context filters for docker_build
.)
If you have multiple Docker images that depend on each other, you can sync files from anywhere within the contexts of any of the images. (In the diagram above, Tilt is building two images; the yellow image in the server1
directory depends on–i.e. FROM
’s–the red image in the common
directory.)
The rule of thumb is: if Tilt is watching it, you can sync
it. (Tilt will watch it if it’s in a docker_build.context
or custom_build.deps
).
Let’s review
For instance, take the following sync statements:
docker_build('my-img', './server', live_update=[
sync('./server/src', '/app'),
sync('./server/package.json', '/app/web/package.json')
])
- change to
./server/src/A.txt
=> synced to/app/A.txt
- change to
./server/package.json
=> synced to/app/web/package.json
- change to
./server/some_file.txt
=> doesn’t match anysync
statements, but does match thedocker_build
context; instead of a Live Update, Tilt performs a Docker Build - change to
./stuff.txt
=> doesn’t match async
statement OR thedocker_build
context; nothing happens
fall_back_on(files: str || List[str])
This step is optional, though if provided, it must come at the beginning of the live_update
call. The argument is a filepath (string) or multiple filepaths (list of strings) on your local machine, either absolute or relative to the Tiltfile. Whenever Tilt detects a change to your local filesystem that would otherwise trigger a LiveUpdate, it first checks if it matches any fall_back_on
files; if yes, instead of doing a LiveUpdate, Tilt falls back to a full rebuild + deploy.
run(cmd: str, trigger=None: str || List[str])
The first argument to run
is a the command to be executed on the running container. Currently, all commands are executed from /
, so be sure to use absolute paths!
Currently, all of your run
steps must come after all of your sync
steps.
If you provide multiple run
steps, the commands will be executed in the order given.
Run triggers
The second arg, trigger
, is optional. If you don’t provide a trigger, then the command is executed on the container whenever Tilt performs a LiveUpdate (i.e. whenever Tilt detects a change to one or more files matching a sync
).
A trigger is a filepath (string) or multiple filepaths (list of strings) on your local machine, either absolute or relative to the Tiltfile. If a trigger is provided, Tilt only executes the command if a changed file matches one the trigger paths.
Note that specifying a file as a trigger does not tell Tilt to watch and/or sync that file; that is, all trigger
files must also be included in a sync
step.
Let’s walk through what will and won’t happen when various files change, given this example Tiltfile:
docker_build('my-img', '.', live_update=[
sync('./src', '/app'),
run('/app/setup.sh'),
run('cd /app/web && yarn install', trigger='./src/web/yarn.lock'),
run('/app/run_configs.sh', trigger=['./configs/foo.yaml', './configs/bar.yaml'])
])
- change to
./src/main.py
=> this file matches a sync step, so we run the Live Update. We runsetup.sh
, because it has no triggers and so runs on every Live Update. We run neither of the otherrun
steps, b/c this file doesn’t match any of their triggers. - change to
./src/web/yarn.lock
=> run the Live Update; runsetup.sh
; runcd /app/web && yarn install
, because this file matches that command’s trigger - change to
./configs/foo.yaml
=> whoops, this file doesn’t match anysync
steps! Even though it matches a trigger (for the thirdrun
), we won’t do a Live Update for this change; instead, we do a full Docker build (see notes onsync
, above, for what changes trigger a Live Update vs. a full build + deploy).
Restarting your Process
Some apps or invocations thereof (e.g. Javascript apps run via nodemon
, or Flask apps run in debug mode) detect and incorporate code changes without needing to restart. For other apps, though, you’ll need to re-execute them for changes to take effect.
For most setups, you’ll be able to use the restart_process
extension: import the extension, replace your docker_build
call with a docker_build_with_restart
call, and specify the entrypoint
parameter (i.e. the command to run on container start and re-run on Live Update).
There are a few exceptions to the above; the restart_process
extension doesn’t work for:
- Docker Compose resources; you should use the
restart_container()
Live Update step instead - Images build via
custom_build
- Container images without a shell (e.g.
scratch
,distroless
) - CRDs
If any of the exceptions above apply to you, or restart_process
doesn’t otherwise work for your use case, read on.
Workarounds for Restarting Your Process
Tilt is flexible enough that you can employ any number of workarounds for restarting your process as part of a Live Update. The basic idea is to invoke your process such that a single command (specified as a run
step) causes it to restart. Here are a few approaches we recommend:
- We’ve written a set of wrappers for your process. Put these scripts in your container and invoke your process as:
/path/to/start.sh /path/to/bin
You can then restart your process with Live Update step:
run('/path/to/restart.sh')
. (Requires that shell be available on your container.) entr
is a neat utility for (re)running processes when specified files change. You can designate an arbitrary file to trigger process restart, say/restart.txt
, and invoke your process like this:echo /restart.txt | entr -rz /path/to/bin
You can then your process with Live Update step:
run('date > /restart.txt')
. (You’ll have to ensure thatentr
is present in your Docker image, and that your arbitrary file for restarting exists.) (Requires that shell be available on your container.)
Recall that you can change the command run by your container in a few ways:
- in the Dockerfile, via
ENTRYPOINT
/CMD
- in your Kubernetes YAML, via
spec.containers.[the_container].command
- in your Tiltfile, via the
docker_build.entrypoint
parameter (or analogously,custom_build.entrypoint
)
More Examples
If you need more specifics on how to set up Live Update with your programming language, all our major example projects use Live Update:
-
The initial build is always a full build because Live Update needs a running container to modify. Thus, your base build (Docker/Custom Build) should be sufficient to create your dev image, and should not rely on any
sync
‘d files orrun
commands. ↩