2024-03-29 17:49:24 +03:00
|
|
|
# Creating an infernet-compatible `hello-world` container
|
|
|
|
|
|
|
|
In this tutorial, we'll create a simple hello-world container that can be used
|
|
|
|
with infernet.
|
|
|
|
|
2024-03-29 17:50:13 +03:00
|
|
|
> [!NOTE]
|
|
|
|
> This directory `containers/hello-world` already includes the final result
|
2024-03-29 17:49:24 +03:00
|
|
|
> of this tutorial. Run the following tutorial in a new directory.
|
|
|
|
|
|
|
|
Let's get started! 🎉
|
|
|
|
|
|
|
|
## Step 1: create a simple flask-app and a requirements.txt file
|
|
|
|
|
|
|
|
First, we'll create a simple flask-app that returns a hello-world message.
|
|
|
|
We begin by creating a `src` directory:
|
|
|
|
|
|
|
|
```
|
|
|
|
mkdir src
|
|
|
|
```
|
|
|
|
|
|
|
|
Inside `src`, we create a `app.py` file with the following content:
|
|
|
|
|
|
|
|
```python
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
from flask import Flask, request
|
|
|
|
|
|
|
|
|
|
|
|
def create_app() -> Flask:
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
|
def index() -> str:
|
|
|
|
return "Hello world service!"
|
|
|
|
|
|
|
|
@app.route("/service_output", methods=["POST"])
|
|
|
|
def inference() -> dict[str, Any]:
|
|
|
|
input = request.json
|
|
|
|
return {"output": f"hello, world!, your input was: {input}"}
|
|
|
|
|
|
|
|
return app
|
|
|
|
```
|
|
|
|
|
|
|
|
As you can see, the app has two endpoints: `/` and `/service_output`. The first
|
|
|
|
one is simply used to ping the service, while the second one is used for infernet.
|
|
|
|
|
|
|
|
We can see that our app uses the `flask` package. Additionally, we'll need to
|
|
|
|
install the `gunicorn` package to run the app. We'll create a `requirements.txt`
|
|
|
|
file with the following content:
|
|
|
|
|
|
|
|
```
|
|
|
|
Flask>=3.0.0,<4.0.0
|
2024-06-06 20:18:48 +03:00
|
|
|
gunicorn>=22.0.0,<23.0.0
|
2024-03-29 17:49:24 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
## Step 2: create a Dockerfile
|
|
|
|
|
|
|
|
Next, we'll create a Dockerfile that builds the flask-app and runs it.
|
|
|
|
At the top-level directory, create a `Dockerfile` with the following content:
|
|
|
|
|
|
|
|
```dockerfile
|
|
|
|
FROM python:3.11-slim as builder
|
|
|
|
|
|
|
|
WORKDIR /app
|
|
|
|
|
|
|
|
ENV PYTHONUNBUFFERED 1
|
|
|
|
ENV PYTHONDONTWRITEBYTECODE 1
|
|
|
|
ENV PYTHONPATH src
|
|
|
|
|
|
|
|
WORKDIR /app
|
|
|
|
|
|
|
|
RUN apt-get update
|
|
|
|
|
|
|
|
COPY src/requirements.txt .
|
|
|
|
|
|
|
|
RUN pip install --upgrade pip && pip install -r requirements.txt
|
|
|
|
|
|
|
|
COPY src src
|
|
|
|
|
|
|
|
ENTRYPOINT ["gunicorn", "app:create_app()"]
|
|
|
|
CMD ["-b", "0.0.0.0:3000"]
|
|
|
|
```
|
|
|
|
|
|
|
|
This is a simple Dockerfile that:
|
|
|
|
|
|
|
|
1. Uses the `python:3.11-slim` image as a base image
|
|
|
|
2. Installs the requirements
|
|
|
|
3. Copies the source code
|
|
|
|
4. Runs the app on port `3000`
|
|
|
|
|
2024-03-29 17:50:13 +03:00
|
|
|
> [!IMPORTANT]
|
2024-03-29 17:49:24 +03:00
|
|
|
> App must be exposed on port `3000`. Infernet's orchestrator
|
|
|
|
> will always assume that the container apps are exposed on that port within the container.
|
|
|
|
> Users can then remap this port to any port that they want on the host machine
|
|
|
|
> using the `port` parameter in the container specs.
|
|
|
|
|
|
|
|
By now, your project directory should look like this:
|
|
|
|
|
|
|
|
```
|
|
|
|
.
|
|
|
|
├── Dockerfile
|
|
|
|
├── README.md
|
|
|
|
└── src
|
|
|
|
├── __init__.py
|
|
|
|
└── app.py
|
|
|
|
└── requirements.txt
|
|
|
|
```
|
|
|
|
|
|
|
|
## Step 3: build the container
|
|
|
|
|
|
|
|
Now, we can build the container. At the top-level directory, run:
|
|
|
|
|
|
|
|
```
|
|
|
|
docker build -t hello-world .
|
|
|
|
```
|
|
|
|
|
|
|
|
## Step 4: run the container
|
|
|
|
|
|
|
|
Finally, we can run the container. In one terminal, run:
|
|
|
|
|
|
|
|
```
|
|
|
|
docker run --rm -p 3000:3000 --name hello hello-world
|
|
|
|
```
|
|
|
|
|
|
|
|
## Step 5: ping the container
|
|
|
|
|
|
|
|
In another terminal, run:
|
|
|
|
|
|
|
|
```
|
2024-03-29 17:50:13 +03:00
|
|
|
curl "localhost:3000"
|
2024-03-29 17:49:24 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
It should return something like:
|
|
|
|
|
|
|
|
```
|
|
|
|
Hello world service!
|
|
|
|
```
|
|
|
|
|
|
|
|
Congratulations! You've created a simple hello-world container that can be
|
|
|
|
used with infernet. 🎉
|
|
|
|
|
|
|
|
## Step 6: request a service output
|
|
|
|
|
|
|
|
Now, let's request a service output. Note that this endpoint is called by
|
|
|
|
the infernet node, not by the user. For debugging purposes however, it's useful to
|
|
|
|
be able to call it manually.
|
|
|
|
|
|
|
|
In your terminal, run:
|
|
|
|
|
|
|
|
```
|
|
|
|
curl -X POST -H "Content-Type: application/json" -d '{"input": "hello"}' localhost:3000/service_output
|
|
|
|
```
|
|
|
|
|
|
|
|
The output should be something like:
|
|
|
|
|
|
|
|
```
|
|
|
|
{"output": "hello, world!, your input was: {'input': 'hello'}"}
|
|
|
|
```
|
|
|
|
|
|
|
|
Your users will never call this endpoint directly. Instead, they will:
|
|
|
|
|
2024-03-29 17:50:13 +03:00
|
|
|
1. Either [create an off-chain job request](../hello-world#L36) through the node API
|
2024-03-29 17:49:24 +03:00
|
|
|
2. Or they will make a subscription on their contracts
|