# Tilt Documentation
# Source: https://docs.tilt.dev/example_python.html
# Path: example_python.html

  * [ Getting Started ](/index.html) [ Getting Started ](/docs_nav_gettingstarted.html)
  * [ Guides ](/tiltfile_authoring.html) [ Guides ](/docs_nav_guides.html)
  * [ Tiltfile & CLI ](/api.html) [ Tiltfile & CLI ](/docs_nav_reference.html)
  * [ Tilt API ](https://api.tilt.dev) [ Tilt API ](https://api.tilt.dev)

Tiltfile

    

  * [ Writing Your First Tiltfile ](/tiltfile_authoring.html)
  * [ Tiltfile Concepts ](/tiltfile_concepts.html)
  * [ Per user Config ](/tiltfile_config.html)
  * [ Many Tiltfiles and Many Repos ](/multiple_repos.html)
  * [ Debugging File Changes ](/file_changes.html)
  * [ Resource Dependencies ](/resource_dependencies.html)
  * [ Manual Update Control ](/manual_update_control.html)
  * [ Disabling Resources new ](/disable_resources.html)

Migrating Existing Projects

    

  * [ Plain Old Static HTML ](/example_static_html.html)
  * [ Go ](/example_go.html)
  * [ Python ](/example_python.html)
  * [ NodeJS ](/example_nodejs.html)
  * [ Java ](/example_java.html)
  * [ Bazel ](/example_bazel.html)
  * [ C# ](/example_csharp.html)

Building Images

    

  * [ Getting Started with Image Builds ](/dependent_images.html)
  * [ Setting up any Image Registry ](/personal_registry.html)
  * [ Custom Image Builders ](/custom_build.html)
  * [ Bazel ](/integrating_bazel_with_tilt.html)
  * [ Skaffold ](/skaffold.html)

Kubernetes Resources

    

  * [ Modifying YAML for Dev new ](/templating.html)
  * [ Installing YAML with Helm ](/helm.html)
  * [ Port Forwards ](/accessing_resource_endpoints.html)
  * [ Custom Resource Definitions ](/custom_resource.html)
  * [ Connecting Debuggers ](/debuggers_python.html)

More Resource Types

    

  * [ Local Commands, Servers, and Tests ](/local_resource.html)
  * [ Docker Compose ](/docker_compose.html)

Live Update

    

  * [ Technical Specifications ](/live_update_reference.html)

Continuous Integration (CI)

    

  * [ Overview ](/ci.html)

Extending Tilt

    

  * [ Custom Buttons ](/buttons.html)
  * [ Tiltfile Extensions ](/extensions.html)
  * [ Contribute Extensions ](/contribute_extension.html)

Tilt with Your Team

    

  * [ Onboarding Checklist ](/onboarding_checklist.html)
  * [ Sharing Snapshots ](/snapshots.html)

#  Example: Python + Flask

The best indicator of a healthy development workflow is a short feedback loop.

Kubernetes is a huge wrench in the works.

Letâs fix this.

In this example, weâre going to take you through a simple âhello worldâ
server written in Python that uses
[Flask](https://palletsprojects.com/p/flask/), a lightweight web application
framework.

Consider watching this companion video if you prefer. Or come back to it
afterward.

Weâll use Tilt to:

  * Run the server on Kubernetes
  * Measure the time from a code change to a new process
  * Optimize that time for faster feedback

This particular example server doesnât do much, but itâs useful to confirm
that Tilt is working as expected in your environment.

All the code is in this repo:

[tilt-example-python](https://github.com/tilt-dev/tilt-example-python)

To skip straight to the fully optimized setup, go to this subdirectory:

[Recommended Setup](https://github.com/tilt-dev/tilt-example-
python/blob/master/3-recommended)

## Step 0: The Simplest Deployment

Our server is just seven lines long, and all it does is serve us an HTML page:

    
    
    from flask import Flask, render_template
    app = Flask(__name__)
    
    
    @app.route("/")
    def serve():
        return render_template("index.html")
    
    
    if __name__ == "__main__":
        app.run(port=8000)
    

To start this server on Kubernetes, we need three config files:

  1. A [Dockerfile](https://github.com/tilt-dev/tilt-example-python/blob/master/0-base/Dockerfile) that builds the image
  2. A [Kubernetes deployment](https://github.com/tilt-dev/tilt-example-python/blob/master/0-base/kubernetes.yaml) that runs the image
  3. And finally, a [Tiltfile](https://github.com/tilt-dev/tilt-example-python/blob/master/0-base/Tiltfile) that ties them together:

    
    
    docker_build('example-python-image', '.')
    k8s_yaml('kubernetes.yaml')
    k8s_resource('example-python', port_forwards=8000)
    

The first line tells Tilt to build an image with the name `example-python-
image` in the current directory.

The second line tells Tilt to load the Kubernetes
[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#creating-
a-deployment) YAML. The image name in the `docker_build` call must match the
container `image` reference in the `example-python` Deployment.

The last line configures port-forwarding so that your server is reachable at
`localhost:8000`. The resource name in the `k8s_resource` call must match the
Deploymentâs `metadata.name` in `kubernetes.yaml`.

Try it! Run:

    
    
    git clone https://github.com/tilt-dev/tilt-example-python
    cd tilt-example-python/0-base
    tilt up
    

Tilt will open a browser showing the web UI, a unified view that shows you app
status and logs. Your terminal will also turn into a status box if youâd
like to watch your server come up there.

When the server is ready, you will see the status icon turn green. The log
pane will display some output from Flask, starting with:

> Serving Flask app âappâ

[ ![](assets/docimg/example-python-0-base.png)
](https://cloud.tilt.dev/snapshot/AcTG0ucLlISGcy3NXSU=) The server is up!
(Click the screenshot to see an interactive view.)

## Step 1: Letâs Add Benchmark Trickery

Before we try to make this faster, letâs measure it.

Using [`local_resource`](local_resource.html), you can direct Tilt to execute
existing scripts or arbitrary shell commands on your own machine, and manage
them from your sidebar like any other Tilt resource. Weâre going to use this
functionality to benchmark our deployments.

First, we add a `local_resource` to our [Tiltfile](https://github.com/tilt-
dev/tilt-example-python/blob/master/1-measured/Tiltfile) that records the
current time, then kicks off an update.

    
    
    # Records the current time, then kicks off a server update.
    # Normally, you would let Tilt do deploys automatically, but this
    # shows you how to set up a custom workflow that measures it.
    local_resource(
      'deploy',
      'date +%s > start-time.txt'
    )
    â¦
    k8s_resource('example-python', port_forwards=8000,
                 resource_deps=['deploy'])
    

The `local_resource()` call creates a local resource named `deploy`. The
second argument is the command that it runs.

Weâve also modified our server itself to read that start time and print the
time elapsed:

    
    
    def get_update_time_secs() -> float:
        with open('/app/start-time.txt', 'r') as file:
            start_ns_since_epoch = float(file.read().strip())
    
        start_secs_since_epoch = start_ns_since_epoch / 10**9
        now_secs_since_epoch = time.time()
    
        return round(now_secs_since_epoch - start_secs_since_epoch, 2)
    ...
    if __name__ == "__main__":
        UPDATE_TIME = get_update_time_secs()
        app.run(port=8000)
    

Whenever the app starts up, it calls `get_update_time_secs()`, does the math
to figure out the time elapsed since the timestamp in `start-time.txt`, and
stores that value in a global variable; when the app serves `index.html`, it
passes that value into the HTML template so that it shows up in the webpage.

See that button next to the `deploy` resource?

![](assets/docimg/)

Letâs click it and see what happens!

[ ![](assets/docimg/example-python-1-measured.png) ](https://cloud.tilt.dev/snapshot/AeTJ0ucLJor0hnfdg7s=) Clicking the button triggers the 'deploy' local_resource, which in turn kicks off an update to the server. (Click the screenshot to see an interactive view.) Approach | Deploy Time1  
---|---  
Naive | 10-11s  
  
If you look closely, the elapsed time displayed in the Tilt sidebar is
different than the benchmark our app logged. Thatâs OK! In microservice
development, there are many benchmarks we care about â the time to build the
image, the time to schedule the process, and the time until the server is
ready to serve traffic.

Tilt offers you some default benchmarks, _and_ the tools to capture your own.

Our benchmarks show this is slow. Can we do better?

## Step 2: Why Is the Docker Build So Slow?

The first thing I notice when I click âdeployâ is a bunch of logs from
`pip install`; and not just once, but _every dang time_. This is a hint that
we can optimize our Dockerfile to be smarter about caching. With a little
rearranging, our [new Dockerfile](https://github.com/tilt-dev/tilt-example-
python/blob/master/2-optimize-dockerfile/Dockerfile) looks like this:

    
    
    ADD requirements.txt .
    RUN pip install -r requirements.txt
    
    ADD . .
    

Hereâs what it looks like when we build with our new Dockerfile:

[ ![](assets/docimg/example-python-2-dockerfile.png)
](https://cloud.tilt.dev/snapshot/AZjdiecL6XcZBu5kO3Y=) Dependency
installation now uses the cache instead of actually running a long, slow
command. (Click the screenshot to see an interactive view.)

Hooray, weâre now using the cache instead of running `pip install` for every
single build. (For more on the principles at work here, [check out this
guide](https://pythonspeed.com/articles/docker-caching-model/).)

Hereâs what our timing looks like now:

Approach | Deploy Time  
---|---  
Naive | 10-11s  
Optimized Dockerfile | 2.5-3.1s  
  
Pretty good! But Tilt has some tricks up its sleeve to make it even faster.

## Step 3: Letâs Optimize It _Even More_

When we make a change to a file, we currently have to build an image, deploy
new Kubernetes configs, and wait for Kubernetes to schedule the pod.

With Tilt, we can skip all of these steps, and instead `live_update` the pod
in place.

[Our new Tiltfile](https://github.com/tilt-dev/tilt-example-
python/blob/master/3-recommended/Tiltfile) contains the following new code:

    
    
    # Add a live_update rule to our docker_build
    congrats = "ð Congrats, you ran a live_update! ð"
    docker_build('example-python-image', '.', build_args={'flask_debug': 'True'},
        live_update=[
            sync('.', '/app'),
            run('cd /app && pip install -r requirements.txt',
                trigger='./requirements.txt'),
    
            # if all that changed was start-time.txt, make sure the server
            # reloads so that it will reflect the new startup time
            run('touch /app/app.py', trigger='./start-time.txt'),
    
            # add a congrats message!
            run('sed -i "s/Hello cats!/{}/g" /app/templates/index.html'.
                format(congrats)),
    ])
    

Weâve added two new parameters to `docker_build()`: `build_args` and
`live_update`. Letâs look at the latter first.

When a `live_update` is triggered, Tilt will, in order:

  1. Sync the code from the current directory (`.`) into the container at directory `/app`.
  2. IF `requirements.txt` has changed, run `pip install`
  3. Poke `app.py` if necessary to make sure that Flask reloads the server
  4. Congratulate you on finishing this guide!

The second additional parameter, `build_args={'flask_debug': 'True'}`,
corresponds to this Dockerfile change:

    
    
    # Default value; will be overridden by build-args, if passed
    ARG flask_debug=False
    
    ENV FLASK_DEBUG $flask_debug
    

Together, these changes mean that when we build this Dockerfile via this
Tiltfile, our Flask app will run in development mode. When in development
mode, Flask watches for file changes and reloads the server when necessary.

Letâs see what this new configuration looks like in action:

[ ![](assets/docimg/example-python-3-liveupdate.png)
](https://cloud.tilt.dev/snapshot/AfLO0ucLqMHzz2JA5ls=) The result of a
live_update. (Click the screenshot to see an interactive view.).

Tilt was able to update the container in less than two seconds! (And a chunk
of that time was overhead from Flask, not from Tilt.)

## Our Recommendation

### Final Score

Approach | Deploy Time  
---|---  
Naive | 10-11s  
Optimized Dockerfile | 2.5-3.1s  
With live_update | 1-2s  
  
You can try the server here:

[Recommended Structure](https://github.com/tilt-dev/tilt-example-
python/blob/master/3-recommended)

This is a very simple example, but we hope it gives you a good starting point
for running your Flask app (or other Python app) via Tilt!

## Further Reading

### CI

Once youâre done configuring your project, set up a CI test to ensure your
setup doesnât break! In the example repo, CircleCI uses
[`ctlptl`](https://github.com/tilt-dev/ctlptl) to create a single-use
Kubernetes cluster. The test script invokes `tilt ci`. The `tilt ci` command
deploys all services in a Tiltfile and exits successfully if theyâre
healthy.

  * [CircleCI config](https://github.com/tilt-dev/tilt-example-python/blob/master/.circleci/config.yml)
  * [Test script](https://github.com/tilt-dev/tilt-example-python/blob/master/test/test.sh)

### Other sample Python projects:

  * [Tilt Avatars](https://github.com/tilt-dev/tilt-avatars), a microservice app with a Python API backend.
  * [abc123](https://github.com/tilt-dev/abc123) is a mini microservice app with a Python server called `numbers`
  * Our [blog post about Live Update](https://blog.tilt.dev/2019/04/02/fast-kubernetes-development-with-live-update.html) includes an [example Python server](https://github.com/tilt-dev/live_update/tree/master/python)
  * [Servantes](https://github.com/tilt-dev/servantes), our multi-language microservice demo app, contains a Python service called `hypothesizer`

### Examples in other languages:

  * [Plain Old Static HTML](/example_static_html.html)
  * [Go](/example_go.html)
  * [NodeJS](/example_nodejs.html)
  * [Java](/example_java.html)
  * [C#](/example_csharp.html)
  * [Bazel](/example_bazel.html)

  1. Tiltâs first deployment of a service takes a few seconds longer than subsequent ones, due to some behind-the-scenes setup. Measurements in this guide focus on non-initial builds.Â ↩

â Back to top  [ Edit on GitHub  ](https://github.com/tilt-
dev/tilt.build/tree/master/docs/example_python.md)







### Was this doc helpful?

Yes No