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).