Skip to content

Commit 2dd33c8

Browse files
committed
Add guide for deployment to Koyeb.
Fix #1369.
1 parent 90db2de commit 2dd33c8

File tree

8 files changed

+208
-4
lines changed

8 files changed

+208
-4
lines changed

docs/howto/fly.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Deploy to Fly
2-
================
2+
=============
33

44
This guide describes how to deploy a websockets server to Fly_.
55

docs/howto/heroku.rst

+1-3
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,10 @@ on websockets:
5858
.. literalinclude:: ../../example/deployment/heroku/requirements.txt
5959
:language: text
6060

61-
Create a ``Procfile``.
61+
Create a ``Procfile`` to tell Heroku how to run the app.
6262

6363
.. literalinclude:: ../../example/deployment/heroku/Procfile
6464

65-
This tells Heroku how to run the app.
66-
6765
Confirm that you created the correct files and commit them to git:
6866

6967
.. code-block:: console

docs/howto/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Once your application is ready, learn how to deploy it on various platforms.
4848
:titlesonly:
4949

5050
render
51+
koyeb
5152
fly
5253
heroku
5354
kubernetes

docs/howto/koyeb.rst

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
Deploy to Koyeb
2+
================
3+
4+
This guide describes how to deploy a websockets server to Koyeb_.
5+
6+
.. _Koyeb: https://www.koyeb.com
7+
8+
.. admonition:: The free tier of Koyeb is sufficient for trying this guide.
9+
:class: tip
10+
11+
The `free tier`__ include one web service, which this guide uses.
12+
13+
__ https://www.koyeb.com/pricing
14+
15+
We’re going to deploy a very simple app. The process would be identical to a
16+
more realistic app.
17+
18+
Create repository
19+
-----------------
20+
21+
Koyeb supports multiple deployment methods. Its quick start guides recommend
22+
git-driven deployment as the first option. Let's initialize a git repository:
23+
24+
.. code-block:: console
25+
26+
$ mkdir websockets-echo
27+
$ cd websockets-echo
28+
$ git init -b main
29+
Initialized empty Git repository in websockets-echo/.git/
30+
$ git commit --allow-empty -m "Initial commit."
31+
[main (root-commit) 740f699] Initial commit.
32+
33+
Render requires the git repository to be hosted at GitHub.
34+
35+
Sign up or log in to GitHub. Create a new repository named ``websockets-echo``.
36+
Don't enable any of the initialization options offered by GitHub. Then, follow
37+
instructions for pushing an existing repository from the command line.
38+
39+
After pushing, refresh your repository's homepage on GitHub. You should see an
40+
empty repository with an empty initial commit.
41+
42+
Create application
43+
------------------
44+
45+
Here’s the implementation of the app, an echo server. Save it in a file
46+
called ``app.py``:
47+
48+
.. literalinclude:: ../../example/deployment/koyeb/app.py
49+
:language: python
50+
51+
This app implements typical requirements for running on a Platform as a Service:
52+
53+
* it listens on the port provided in the ``$PORT`` environment variable;
54+
* it provides a health check at ``/healthz``;
55+
* it closes connections and exits cleanly when it receives a ``SIGTERM`` signal;
56+
while not documented, this is how Koyeb terminates apps.
57+
58+
Create a ``requirements.txt`` file containing this line to declare a dependency
59+
on websockets:
60+
61+
.. literalinclude:: ../../example/deployment/koyeb/requirements.txt
62+
:language: text
63+
64+
Create a ``Procfile`` to tell Koyeb how to run the app.
65+
66+
.. literalinclude:: ../../example/deployment/koyeb/Procfile
67+
68+
Confirm that you created the correct files and commit them to git:
69+
70+
.. code-block:: console
71+
72+
$ ls
73+
Procfile app.py requirements.txt
74+
$ git add .
75+
$ git commit -m "Initial implementation."
76+
[main f634b8b] Initial implementation.
77+
 3 files changed, 39 insertions(+)
78+
 create mode 100644 Procfile
79+
 create mode 100644 app.py
80+
 create mode 100644 requirements.txt
81+
82+
The app is ready. Let's deploy it!
83+
84+
Deploy application
85+
------------------
86+
87+
Sign up or log in to Koyeb.
88+
89+
In the Koyeb control panel, create a web service with GitHub as the deployment
90+
method. Install and authorize Koyeb's GitHub app if you haven't done that yet.
91+
92+
Follow the steps to create a new service:
93+
94+
1. Select the ``websockets-echo`` repository in the list of your repositories.
95+
2. Confirm that the **Free** instance type is selected. Click **Next**.
96+
3. Configure health checks: change the protocol from TCP to HTTP and set the
97+
path to ``/healthz``. Review other settings; defaults should be correct.
98+
Click **Deploy**.
99+
100+
Koyeb builds the app, deploys it, verifies that the health checks passes, and
101+
makes the deployment active.
102+
103+
Validate deployment
104+
-------------------
105+
106+
Let's confirm that your application is running as expected.
107+
108+
Since it's a WebSocket server, you need a WebSocket client, such as the
109+
interactive client that comes with websockets.
110+
111+
If you're currently building a websockets server, perhaps you're already in a
112+
virtualenv where websockets is installed. If not, you can install it in a new
113+
virtualenv as follows:
114+
115+
.. code-block:: console
116+
117+
$ python -m venv websockets-client
118+
$ . websockets-client/bin/activate
119+
$ pip install websockets
120+
121+
Look for the URL of your app in the Koyeb control panel. It looks like
122+
``https://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/``. Connect the
123+
interactive client — you must replace ``https`` with ``wss`` in the URL:
124+
125+
.. code-block:: console
126+
127+
$ python -m websockets wss://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/
128+
Connected to wss://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/.
129+
>
130+
131+
Great! Your app is running!
132+
133+
Once you're connected, you can send any message and the server will echo it,
134+
or press Ctrl-D to terminate the connection:
135+
136+
.. code-block:: console
137+
138+
> Hello!
139+
< Hello!
140+
Connection closed: 1000 (OK).
141+
142+
143+
You can also confirm that your application shuts down gracefully.
144+
145+
Connect an interactive client again:
146+
147+
.. code-block:: console
148+
149+
$ python -m websockets wss://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/
150+
Connected to wss://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/.
151+
>
152+
153+
In the Koyeb control panel, go to the **Settings** tab, click **Pause**, and
154+
confirm.
155+
156+
Eventually, the connection gets closed with code 1001 (going away).
157+
158+
.. code-block:: console
159+
160+
$ python -m websockets wss://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/
161+
Connected to wss://<koyeb-app>-<github-user>-<koyeb-app-id>.koyeb.app/.
162+
Connection closed: 1001 (going away).
163+
164+
If graceful shutdown wasn't working, the server wouldn't perform a closing
165+
handshake and the connection would be closed with code 1006 (abnormal closure).

docs/spelling_wordlist.txt

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ iterable
3838
js
3939
keepalive
4040
KiB
41+
Koyeb
4142
kubernetes
4243
lifecycle
4344
linkerd

example/deployment/koyeb/Procfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: python app.py

example/deployment/koyeb/app.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python
2+
3+
import asyncio
4+
import http
5+
import os
6+
import signal
7+
8+
from websockets.asyncio.server import serve
9+
10+
11+
async def echo(websocket):
12+
async for message in websocket:
13+
await websocket.send(message)
14+
15+
16+
def health_check(connection, request):
17+
if request.path == "/healthz":
18+
return connection.respond(http.HTTPStatus.OK, "OK\n")
19+
20+
21+
async def main():
22+
# Set the stop condition when receiving SIGINT.
23+
loop = asyncio.get_running_loop()
24+
stop = loop.create_future()
25+
loop.add_signal_handler(signal.SIGINT, stop.set_result, None)
26+
27+
async with serve(
28+
echo,
29+
host="",
30+
port=int(os.environ["PORT"]),
31+
process_request=health_check,
32+
):
33+
await stop
34+
35+
36+
if __name__ == "__main__":
37+
asyncio.run(main())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
websockets

0 commit comments

Comments
 (0)