Deploy to Heroku

This guide describes how to deploy a websockets server to Heroku. The same principles should apply to other Platform as a Service providers.

Heroku no longer offers a free tier.

When this tutorial was written, in September 2021, Heroku offered a free tier where a websockets app could run at no cost. In November 2022, Heroku removed the free tier, making it impossible to maintain this document. As a consequence, it isn’t updated anymore and may be removed in the future.

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

Create repository

Deploying to Heroku requires a git repository. Let’s initialize one:

$ mkdir websockets-echo
$ cd websockets-echo
$ git init -b main
Initialized empty Git repository in websockets-echo/.git/
$ git commit --allow-empty -m "Initial commit."
[main (root-commit) 1e7947d] Initial commit.

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 signal
import os

from websockets.asyncio.server import serve


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


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 serve(
        echo,
        host="",
        port=int(os.environ["PORT"]),
    ):
        await stop


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

Heroku expects the server to listen on a specific port, which is provided in the $PORT environment variable. The app reads it and passes it to serve().

Heroku sends a SIGTERM signal to all processes when shutting down a dyno. When the app receives this signal, it closes connections and exits cleanly.

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

websockets

Create a Procfile.

web: python app.py

This tells Heroku how to run the app.

Confirm that you created the correct files and commit them to git:

$ ls
Procfile         app.py           requirements.txt
$ git add .
$ git commit -m "Initial implementation."
[main 8418c62] Initial implementation.
 3 files changed, 32 insertions(+)
 create mode 100644 Procfile
 create mode 100644 app.py
 create mode 100644 requirements.txt

The app is ready. Let’s deploy it!

Deploy application

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

Sign up or log in to Heroku.

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

$ heroku create websockets-echo
Creating ⬢ websockets-echo... done
https://websockets-echo.herokuapp.com/ | https://git.heroku.com/websockets-echo.git
$ git push heroku

... lots of output...

remote: -----> Launching...
remote:        Released v1
remote:        https://websockets-echo.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/websockets-echo.git
 * [new branch]      main -> main

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 Heroku app in this command:

$ python -m websockets wss://websockets-echo.herokuapp.com/
Connected to wss://websockets-echo.herokuapp.com/.
>

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.herokuapp.com/
Connected to wss://websockets-echo.herokuapp.com/.
>

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

$ heroku dyno:restart -a websockets-echo
Restarting dynos on ⬢ websockets-echo... done

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

$ python -m websockets wss://websockets-echo.herokuapp.com/
Connected to wss://websockets-echo.herokuapp.com/.
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).