Tutorial: Build and package a multi-platform desktop app in Python
Tags: beginners, python, flutter, desktop
Published: 2022-06-28 21:24:30

In this tutorial we will show you, step-by-step, how to create a Calculator app in Python using Flet framework and package it as a standalone executable for Windows, macOS and Linux, or deploy it as a web app. The app is a [a simple console program](https://github.com/flet-dev/examples/blob/main/python/tutorials/calc/calc.py), yet it is a multi-platform application with similar to iPhone calculator app UI:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/batr7kpmgohympf9y9hy.gif)

You can find the live demo [here](https://flet-calc.fly.dev/).

In this tutorial, we will cover all of the basic concepts for creating a multi-platform desktop app: building a page layout, adding controls, handling events, and packaging/deployment options.

The tutorial consists of the following steps:
* [Step 1: Getting started with Flet](#getting-started-with-flet)
* [Step 2: Adding page controls](#adding-page-controls)
* [Step 3: Building page layout](#building-page-layout)
* [Step 4: Handling events](#handling-events)
* [Step 5: Packaging as a desktop app](#packaging-as-a-desktop-app)
* [Step 6: Deploying a web app](#deploying-a-web-app)
* [Summary](#summary)

#Step 1: Getting started with Flet <a name="getting-started-with-flet"></a>

To write a Flet web app you don't need to know HTML, CSS or JavaScript, but you do need a basic knowledge of Python and object-oriented programming.

Flet requires Python 3.7 or above. To create a web app in Python with Flet, you need to install `flet` module first:

```bash
pip install flet
```

To start, let's create a simple hello-world app.

Create `hello.py` with the following contents:

```python 
import flet
from flet import Page, Text

def main(page: Page):
    page.add(Text(value="Hello, world!"))

flet.app(target=main)
```

Run this app and you will see a new window with a greeting:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0558ag8z2h4lz14aruz.png)

#Step 2: Adding page controls <a name="adding-page-controls"></a>

Now you are ready to create a calculator app.

To start, you'll need a [Text](https://flet.dev/docs/controls/text) control for showing the result of calculation, and a few [ElevatedButtons](https://flet.dev/docs/controls/elevatedbutton) with all the numbers and actions on them.

Create `calc.py` with the following contents:

```python
import flet
from flet import ElevatedButton, Page, Text

def main(page: Page):
    page.title = "Calc App"
    result = Text(value="0")
    page.add(
        result,
        ElevatedButton(text="AC"),
        ElevatedButton(text="+/-"),
        ElevatedButton(text="%"),
        ElevatedButton(text="/"),
        ElevatedButton(text="7"),
        ElevatedButton(text="8"),
        ElevatedButton(text="9"),
        ElevatedButton(text="*"),
        ElevatedButton(text="4"),
        ElevatedButton(text="5"),
        ElevatedButton(text="6"),
        ElevatedButton(text="-"),
        ElevatedButton(text="1"),
        ElevatedButton(text="2"),
        ElevatedButton(text="3"),
        ElevatedButton(text="+"),
        ElevatedButton(text="0"),
        ElevatedButton(text="."),
        ElevatedButton(text="="),
    )

flet.app(target=main)
```
Run the app and you should see a page like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z94ffzhyqlamx7l4ltmy.png)

#Step 3: Building page layout <a name="building-page-layout"></a>

Now let's arrange the text and buttons in 6 horizontal [rows](https://flet.dev/docs/controls/row).

Replace `calc.py` contents with the following:

```python
import flet
from flet import ElevatedButton, Page, Row, Text

def main(page: Page):
    page.title = "Calc App"
    result = Text(value="0")
    page.add(
        Row(controls=[result]),
        Row(
            controls=[
                ElevatedButton(text="AC"),
                ElevatedButton(text="+/-"),
                ElevatedButton(text="%"),
                ElevatedButton(text="/"),
            ]
        ),
        Row(
            controls=[
                ElevatedButton(text="7"),
                ElevatedButton(text="8"),
                ElevatedButton(text="9"),
                ElevatedButton(text="*"),
            ]
        ),
        Row(
            controls=[
                ElevatedButton(text="4"),
                ElevatedButton(text="5"),
                ElevatedButton(text="6"),
                ElevatedButton(text="-"),
            ]
        ),
        Row(
            controls=[
                ElevatedButton(text="1"),
                ElevatedButton(text="2"),
                ElevatedButton(text="3"),
                ElevatedButton(text="+"),
            ]
        ),
        Row(
            controls=[
                ElevatedButton(text="0"),
                ElevatedButton(text="."),
                ElevatedButton(text="="),
            ]
        ),
    )

flet.app(target=main)
```

Run the app and you should see a page like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8q5eig87yo00pcg8tiyg.png)

##Using Container for decoration

To add a black background with rounded border around the calculator, we will be using [Container](/docs/controls/container) control. Container may decorate only one control, so we will need to wrap all the 6 rows into a single vertical [Column](https://flet.dev/docs/controls/container) that will be used as the container's `content`:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnfqk9xjtvxumq2fi15j.png)

To complete the UI portion of the program, update `color` and `size` properties for the Text, and `color` and `bgcolor` properties for the buttons. For even alignment of the buttons within the rows, we will be using `expand` property as shown on the diagram above. 

Replace `calc.py` contents with the following:
```python
import flet
from flet import (
    Column,
    Container,
    ElevatedButton,
    Page,
    Row,
    Text,
    border_radius,
    colors,
)

def main(page: Page):
    page.title = "Calc App"
    result = Text(value="0", color=colors.WHITE, size=20)
    page.add(
        Container(
            width=300,
            bgcolor=colors.BLACK,
            border_radius=border_radius.all(20),
            padding=20,
            content=Column(
                controls=[
                    Row(controls=[result], alignment="end"),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="AC",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="+/-",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="%",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="/",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="7",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="8",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="9",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="*",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="4",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="5",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="6",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="-",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="1",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="2",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="3",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="+",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="0",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=2,
                            ),
                            ElevatedButton(
                                text=".",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="=",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                ]
            ),
        )
    )

flet.app(target=main)
``` 

Run the app and you should see a page like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rocbvsbz1qffgtqq92js.png)

Just what we wanted!

##Reusable UI components <a name="reusable-ui-components"></a>

While you can continue writing your app in the `main` function, the best practice would be to create a reusable UI component. 

Imagine you are working on an app header, a side menu, or UI that will be a part of a larger project (for example, at Flet we will be using this Calculator app in a bigger "Gallery" app that will show all the examples for Flet framework). 

Even if you can't think of such uses right now, we still recommend creating all your web apps with composability and reusability in mind. 

To make a reusable Calc app component, we are going to encapsulate its state and presentation logic in a separate `CalculatorApp` class.

Replace `calc.py` contents with the following:
```python
import flet
from flet import (
    Column,
    Container,
    ElevatedButton,
    Page,
    Row,
    Text,
    UserControl,
    border_radius,
    colors,
)

class CalculatorApp(UserControl):
    def build(self):
        result = Text(value="0", color=colors.WHITE, size=20)

        # application's root control (i.e. "view") containing all other controls
        return Container(
            width=300,
            bgcolor=colors.BLACK,
            border_radius=border_radius.all(20),
            padding=20,
            content=Column(
                controls=[
                    Row(controls=[result], alignment="end"),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="AC",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="+/-",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="%",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="/",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="7",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="8",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="9",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="*",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="4",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="5",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="6",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="-",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="1",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="2",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="3",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="+",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="0",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=2,
                            ),
                            ElevatedButton(
                                text=".",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                            ),
                            ElevatedButton(
                                text="=",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                            ),
                        ]
                    ),
                ]
            ),
        )

def main(page: Page):
    page.title = "Calc App"
    # create application instance
    calc = CalculatorApp()

    # add application's root control to the page
    page.add(calc)

flet.app(target=main)
```

Read more about [creating user controls](https://flet.dev/docs/getting-started/python#user-controls).

> _Try something_
> Try adding two `CalculatorApp` components to the page:
```python
# create application instance
calc1 = CalculatorApp()
calc2 = CalculatorApp()

# add application's root control to the page
page.add(calc1, calc2)
```

#Step 4: Handling events <a name="handling-events"></a>

Now let's make the calculator do its job. We will be using the same event handler for all the buttons and use `data` property to differentiate between the actions depending on the button clicked. For each ElevatedButton control, specify `on_click=self.button_clicked` event and set `data` property equal to button's text, for example:

```python
ElevatedButton(
    text="AC",
    bgcolor=colors.BLUE_GREY_100,
    color=colors.BLACK,
    expand=1,
    on_click=self.button_clicked,
    data="AC",
)
```

Below is `on_click` event handler that will reset the Text value when "AC" button is clicked:
```python
def button_clicked(self, e):
    if e.data == "AC":
        self.result.value = "0"
            
```

With similar approach, specify `on_click` event and `data` property for each button and add expected action to the `button_clicked` event handler depending on `e.data` value. Copy the entire code for this step from [here](https://github.com/flet-dev/examples/blob/main/python/tutorials/calc/calc.py).

Run the app and see it in the action:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7gklknmt89sjbkjnajp.gif)

#Step 5: Packaging as a desktop app <a name="packaging-as-a-desktop-app"></a>

Congratulations! You have created your Calculator app with Flet, and it looks awesome! Now it's time to share your app with the world!

Flet Python app and all its dependencies can be packaged into an executable and user can run it on their computer without installing a Python interpreter or any modules.

[PyInstaller](https://pyinstaller.org/en/stable/index.html) is used to package Flet Python app and all its dependencies into a single package for Windows, macOS and Linux. To create Windows package, PyInstaller must be run on Windows; to build Linux app, it must be run on Linux; and to build macOS app - on macOS.

Start from installing PyInstaller:

```
pip install pyinstaller
```

Navigate to the directory where your `.py` file is located and build your app with the following command:

```
pyinstaller your_program.py
```

Your bundled Flet app should now be available in `dist/your_program` folder. Try running the program to see if it works.

On macOS/Linux:

```
./dist/your_program/your_program
```

on Windows:

```
dist\your_program\your_program.exe
```

Now you can just zip the contents of `dist/your_program` folder and distribute to your users! They don't need Python or Flet installed to run your packaged program - what a great alternative to Electron!

You'll notice though when you run a packaged program from macOS Finder or Windows Explorer a new console window is opened and then a window with app UI on top of it.

You can hide that console window by rebuilding the package with `--noconsole` switch:

```
pyinstaller your_program.py --noconsole --noconfirm
```

##Bundling to one file <a name="building-to-one-file"></a>

Contents of `dist/your_program` directory is an app executable plus supporting resources: Python runtime, modules, libraries.

You can package all these in a single executable by using `--onefile` switch:

```
pyinstaller your_program.py --noconsole --noconfirm --onefile
```

You'll get a larger executable in `dist` folder. That executable is a self-running archive with your program and runtime resources which gets unpacked into temp directory when run - that's why it takes longer to start "onefile" package.

> _Note:_ 
> For macOS you can distribute either `dist/your_program` or `dist/your_program.app` which is an application bundle.

##Customizing package icon <a name="customizing-package-icon"></a>

Default bundle app icon is diskette which might be confusing for younger developers missed those ancient times when [floppy disks](https://en.wikipedia.org/wiki/Floppy_disk) were used to store computer data.

You can replace the icon with your own by adding `--icon` argument:

```
pyinstaller your_program.py --noconsole --noconfirm --onefile --icon <your-image.png>
```

PyInstaller will convert provided PNG to a platform specific format (`.ico` for Windows and `.icns` for macOS), but you need to install [Pillow](https://pillow.readthedocs.io/en/stable/) module for that:

```
pip install pillow
```

##Packaging assets <a name="packaging assets"></a>

Your Flet app can include [assets](https://flet.dev/docs/controls/image#src). Provided app assets are in `assets` folder next to `your_program.py` they can be added to an application package with `--add-data` argument, on macOS/Linux:

```
pyinstaller your_program.py --noconsole --noconfirm --onefile --add-data "assets:assets"
```

On Windows `assets;assets` must be delimited with `;`:

```
pyinstaller your_program.py --noconsole --noconfirm --onefile --add-data "assets;assets"
```

> _Note:_
> You can find here [all PyInstaller command-line options](https://pyinstaller.org/en/stable/usage.html)

##Using CI for multi-platform packaging <a name="using-ci-for-multi-platform-packaging"></a>

To create an app package with PyInstaller for specific OS it must be run on that OS.

If you don't have an access to Mac or PC you can bundle your app for all three platforms with [AppVeyor](https://www.appveyor.com) - Continuous Integration service for Windows, Linux and macOS. In short, Continuous Integration (CI) is an automated process of building, testing and deploying (Continuous Delivery - CD) application on every push to a repository.

AppVeyor is free for open source projects hosted on GitHub, GitLab and Bitbucket. To use AppVeyor, push your app to a repository within one of those source-control providers.

To get started with AppVeyor [sign up for a free account](https://ci.appveyor.com/signup).

Click "New project" button, authorize AppVeyor to access your GitHub, GitLab or Bitbucket account, choose a repository with your program and create a new project.

Now, to configure packaging of your app for Windows, Linux and macOS, add file with [the following contents](https://github.com/flet-dev/python-ci-example/blob/main/appveyor.yml) into the root of your repository `appveyor.yml`. `appveyor.yml` is a build configuration file, or CI workflow, describing build, test, packaging and deploy commands that must be run on every commit.

> _Note:_
> You can just fork [flet-dev/python-ci-example](https://github.com/flet-dev/python-ci-example) repository and customize it to your needs.

When you push any changes to GitHub repository, AppVeyor will automatically start a new build:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o5dh6ggt9rq7ebfpnuy.png)

What that [CI workflow](https://ci.appveyor.com/project/flet-dev/python-ci-example) does on every push to the repository:

* Clones the repository to a clean virtual machine.
* Installs app dependencies using `pip`.
* Runs `pyinstaller` to package Python app into "onefile" bundle for **Windows**, **macOS** and **Ubuntu**.
* Zip/Tar packages and uploads them to ["Artifacts"](https://ci.appveyor.com/project/flet-dev/python-ci-example/build/job/g2j2lhstv04eyxcm/artifacts).
* Uploads packages to [**GitHub releases**](https://github.com/flet-dev/python-ci-example/releases) when a new tag is pushed. Just push a new tag to make a release!

> _GITHUB_TOKEN_
> `GITHUB_TOKEN` in `appveyor.yml` is a GitHub Personal Access Token (PAT) used by AppVeyor to publish created packages to repository "Releases". You need to generate your own token and replace it in `appveyor.yml`. Login to your GitHub account and navigate to [Personal access token](https://github.com/settings/tokens) page. Click "Generate new token" and select "public_repo" or "repo" scope for public or private repository respectively. Copy generated token to a clipboard and return to AppVeyor Portal. Navigate to [Encrypt configuration data](https://ci.appveyor.com/tools/encrypt) page and paste token to "Value to encrypt" field, click "Encrypt" button. Put encrypted value under `GITHUB_TOKEN` in your `appveyor.yml`.

Configure AppVeyor for your Python project, push a new tag to a repository and "automagically" get desktop bundle for all three platforms in GitHub releases! 🎉

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22axhkidepk2lvpiu728.png)

In addition to [GitHub Releases](https://www.appveyor.com/docs/deployment/github/), you can also configure releasing of artifacts to [Amazon S3 bucket](https://www.appveyor.com/docs/deployment/amazon-s3/) or [Azure Blob storage](https://www.appveyor.com/docs/deployment/azure-blob/).

#Step 6: Deploying a web app <a name="deploying-a-web-app"></a>

Flet app can be deployed as a "standalone" web app which means both your Python app and Flet web server are deployed together as a bundle.

Flet apps use WebSockets for real-time partial updates of their UI and sending events back to your program.
When choosing a hosting provider for your Flet app you should pay attention to their support of WebSockets. Sometimes WebSockets are not allowed or come as a part of more expensive offering, sometimes there is a proxy that periodically breaks WebSocket connection by a timeout (Flet implements re-connection logic, but it could be unpleasant behavior for users of your app anyway).

Another important factor while choosing a hosting provider for Flet app is latency. Every user action on UI sends a message to Flet app and the app sends updated UI back to user. Make sure your hosting provider has multiple data centers, so you can run your app closer to the majority of your users.

> _Note:_
> We are not affiliated with hosting providers in this section - we just use their service and love it.

##Fly.io <a name="fly.io"></a>
[Fly.io](https://fly.io) has robust WebSocket support and can deploy your app to a [data center](https://fly.io/docs/reference/regions/) that is close to your users. They have very attractive pricing with a [generous free tier](https://fly.io/docs/about/pricing/#free-allowances) which allows you to host up to 3 applications for free.

To get started with Fly install [flyctl](https://fly.io/docs/getting-started/installing-flyctl/) and then authenticate:

    flyctl auth login

To deploy the app with `flyctl` you have to add the following 3 files into the folder with your Python app.

Create `requirements.txt` with a list of application dependencies. At minimum it should contain `flet` module:

```txt
flet>=0.1.33
```

Create `fly.toml` describing Fly application:
```toml
app = "<your-app-name>"

kill_signal = "SIGINT"
kill_timeout = 5
processes = []

[env]
  FLET_SERVER_PORT = "8080"

[experimental]
  allowed_public_ports = []
  auto_rollback = true

[[services]]
  http_checks = []
  internal_port = 8080
  processes = ["app"]
  protocol = "tcp"
  script_checks = []

  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
    type = "connections"

  [[services.ports]]
    force_https = true
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

  [[services.tcp_checks]]
    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"
```

Replace `<your-app-name>` with desired application name which will be also used in application URL, such as `https://<your-app-name>.fly.dev`.

Note we are setting the value of `FLET_SERVER_PORT` environment variable to `8080` which is an internal TCP port Flet web app is going to run on.

Create `Dockerfile` containing the commands to build your application container:

```Dockerfile
FROM python:3-alpine

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "./main.py"]
```

`main.py` is a file with your Python program.

> _Note:_
> Fly.io deploys every app as a Docker container, but a great thing about Fly is that it provides a free remote Docker builder, so you don't need Docker installed on your machine.

Next, switch command line to a folder with your app and run the following command to create and initialize a new Fly app:

    flyctl apps create --name <your-app-name>

Deploy the app by running:

    flyctl deploy

That's it! Open your app in the browser by running:

```
flyctl apps open
```

##Replit <a name="replit"></a>

[Replit](https://replit.com/) is an online IDE and hosting platform for web apps written in any language. Their free tier allows running any number of apps with some performance limitations.

To run your app on Replit:

* [Sign up](https://replit.com/signup?from=landing) on Replit.
* Click "New repl" button.
* Select "Python" language from a list and provide repl name, e.g. `my-app`.
* Click "Packages" tab and search for `flet` package; select its latest version.
* Click "Secrets" tab and add `FLET_SERVER_PORT` variable with value `5000`.
* Switch back to "Files" tab and copy-paste your app into `main.py`.
* Run the app. Enjoy.

#Summary <a name="summary"></a>

In this tutorial you have learned how to:

* Create a simple Flet app;
* Work with Reusable UI components;
* Design UI layout using `Column`, `Row` and `Container` controls;
* Handle events;
* Package your Flet app into an executable;
* Deploy your Flet app to the web;

For further reading you can explore [controls](https://flet.dev/docs/controls) and [examples repository](https://github.com/flet-dev/examples/tree/main/python).

We would love to hear your feedback! Please drop us an [email](mailto:hello@flet.dev), join the discussion on [Discord](https://discord.gg/dzWXP8SHG8), follow on [Twitter](https://twitter.com/fletdev).