Deploy to Fly#

This guide describes how to deploy a websockets server to Fly.

The free tier of Fly is sufficient for trying this guide.

The free tier include up to three small VMs. This guide uses only one.

We’re going to deploy a very simple app. The process would be identical for a more realistic app.

Create application#

Here’s the implementation of the app, an echo server. Save it in a file called app.py:

#!/usr/bin/env python

import asyncio
import http
import signal

import websockets


async def echo(websocket):
    async for message in websocket:
        await websocket.send(message)


async def health_check(path, request_headers):
    if path == "/healthz":
        return http.HTTPStatus.OK, [], b"OK\n"


async def main():
    # Set the stop condition when receiving SIGTERM.
    loop = asyncio.get_running_loop()
    stop = loop.create_future()
    loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)

    async with websockets.serve(
        echo,
        host="",
        port=8080,
        process_request=health_check,
    ):
        await stop


if __name__ == "__main__":
    asyncio.run(main())

This app implements typical requirements for running on a Platform as a Service:

  • it provides a health check at /healthz;

  • it closes connections and exits cleanly when it receives a SIGTERM signal.

Create a requirements.txt file containing this line to declare a dependency on websockets:

websockets

The app is ready. Let’s deploy it!

Deploy application#

Follow the instructions to install the Fly CLI, if you haven’t done that yet.

Sign up or log in to Fly.

Launch the app — you’ll have to pick a different name because I’m already using websockets-echo:

$ fly launch
Creating app in ...
Scanning source code
Detected a Python app
Using the following build configuration:
    Builder: paketobuildpacks/builder:base
? App Name (leave blank to use an auto-generated name): websockets-echo
? Select organization: ...
? Select region: ...
Created app websockets-echo in organization ...
Wrote config file fly.toml
? Would you like to set up a Postgresql database now? No
We have generated a simple Procfile for you. Modify it to fit your needs and run "fly deploy" to deploy your application.

This will build the image with a generic buildpack.

Fly can build images with a Dockerfile or a buildpack. Here, fly launch configures a generic Paketo buildpack.

If you’d rather package the app with a Dockerfile, check out the guide to containerize an application.

Replace the auto-generated fly.toml with:

app = "websockets-echo"
kill_signal = "SIGTERM"

[build]
  builder = "paketobuildpacks/builder:base"

[[services]]
  internal_port = 8080
  protocol = "tcp"

  [[services.http_checks]]
    path = "/healthz"

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

This configuration:

  • listens on port 443, terminates TLS, and forwards to the app on port 8080;

  • declares a health check at /healthz;

  • requests a SIGTERM for terminating the app.

Replace the auto-generated Procfile with:

web: python app.py

This tells Fly how to run the app.

Now you can deploy it:

$ fly deploy

... lots of output...

==> Monitoring deployment

1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully

Validate deployment#

Let’s confirm that your application is running as expected.

Since it’s a WebSocket server, you need a WebSocket client, such as the interactive client that comes with websockets.

If you’re currently building a websockets server, perhaps you’re already in a virtualenv where websockets is installed. If not, you can install it in a new virtualenv as follows:

$ python -m venv websockets-client
$ . websockets-client/bin/activate
$ pip install websockets

Connect the interactive client — you must replace websockets-echo with the name of your Fly app in this command:

$ python -m websockets wss://websockets-echo.fly.dev/
Connected to wss://websockets-echo.fly.dev/.
>

Great! Your app is running!

Once you’re connected, you can send any message and the server will echo it, or press Ctrl-D to terminate the connection:

> Hello!
< Hello!
Connection closed: 1000 (OK).

You can also confirm that your application shuts down gracefully.

Connect an interactive client again — remember to replace websockets-echo with your app:

$ python -m websockets wss://websockets-echo.fly.dev/
Connected to wss://websockets-echo.fly.dev/.
>

In another shell, restart the app — again, replace websockets-echo with your app:

$ fly restart websockets-echo
websockets-echo is being restarted

Go back to the first shell. The connection is closed with code 1001 (going away).

$ python -m websockets wss://websockets-echo.fly.dev/
Connected to wss://websockets-echo.fly.dev/.
Connection closed: 1001 (going away).

If graceful shutdown wasn’t working, the server wouldn’t perform a closing handshake and the connection would be closed with code 1006 (abnormal closure).