# Deploying Jina Serve Open-Source Neural Search and AI Serving Framework on Ubuntu 24.04

> Source: <https://dev.to/vultr/deploying-jina-serve-open-source-neural-search-and-ai-serving-framework-on-ubuntu-2404-1m8g>
> Published: 2026-06-16 20:55:00+00:00

Jina Serve is an open-source framework for building neural search and multimodal AI applications, with a cloud-native runtime that handles dynamic batching, async streaming, and microservice orchestration. This guide deploys a Jina Flow with a custom text executor using Docker Compose, with Traefik handling automatic HTTPS in front of the gateway. By the end, you'll have a Jina Flow serving an `/index`

and `/search`

API securely at your domain.

**1. Create the project directory structure:**

``` bash
$ mkdir -p ~/jina-serve/executor
$ cd ~/jina-serve
```

**2. Create the executor module:**

``` bash
$ nano executor/executor.py
python
from jina import Executor, requests
from docarray import BaseDoc, DocList

class TextDoc(BaseDoc):
    text: str = ""

class TextProcessor(Executor):
    @requests(on="/index")
    def index(self, docs: DocList[TextDoc], **kwargs) -> DocList[TextDoc]:
        for doc in docs:
            if doc.text:
                doc.text = doc.text.upper()
        return docs

    @requests(on="/search")
    def search(self, docs: DocList[TextDoc], **kwargs) -> DocList[TextDoc]:
        return docs
```

**3. Pin the executor's Python dependencies:**

``` bash
$ nano executor/requirements.txt
jina==3.34.0
docarray>=0.40.0
```

**4. Wire the executor into Jina's loader:**

``` bash
$ nano executor/config.yml
jtype: TextProcessor
py_modules:
  - executor.py
```

**5. Define the Flow:**

``` bash
$ nano flow.yml
jtype: Flow
with:
  protocol: http
  port: 8080

executors:
  - name: textprocessor
    uses: executor/config.yml
```

**6. Build the container image with the Flow baked in:**

``` bash
$ nano Dockerfile
FROM jinaai/jina:3.34.0-py39-standard

WORKDIR /app

COPY executor/requirements.txt /app/executor/requirements.txt
RUN pip install --no-cache-dir -r /app/executor/requirements.txt

COPY executor /app/executor
COPY flow.yml /app/flow.yml

ENTRYPOINT ["jina", "flow", "--uses", "flow.yml"]
```

**7. Create the environment file:**

``` bash
$ nano .env
DOMAIN=jina.example.com
LETSENCRYPT_EMAIL=admin@example.com
JINA_LOG_LEVEL=INFO
```

**1. Create the Compose manifest:**

``` bash
$ nano docker-compose.yml
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    restart: unless-stopped

  jina:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: jina-flow
    environment:
      - JINA_LOG_LEVEL=${JINA_LOG_LEVEL}
    expose:
      - "8080"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jina.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.jina.entrypoints=websecure"
      - "traefik.http.routers.jina.tls.certresolver=letsencrypt"
      - "traefik.http.services.jina.loadbalancer.server.port=8080"
    restart: unless-stopped
```

**2. Build and start the stack:**

``` bash
$ docker compose up -d --build
```

**3. Verify the services and tail logs:**

``` bash
$ docker compose ps
$ docker compose logs
```

**1. Check the status endpoint:**

``` bash
$ curl https://jina.example.com/status
{"jina":{"jina":"3.34.0","docarray":"0.40.1"...
```

**2. POST a document to /index:**

``` bash
$ curl -X POST https://jina.example.com/index \
    -H "Content-Type: application/json" \
    -d '{"data": [{"text": "hello world"}]}'
{"data":[{"id":null,"text":"HELLO WORLD"}],...}
```

**3. POST to /search to see the passthrough endpoint:**

``` bash
$ curl -X POST https://jina.example.com/search \
    -H "Content-Type: application/json" \
    -d '{"data": [{"text": "test message"}]}'
```

**4. Submit a batch of documents:**

``` bash
$ curl -X POST https://jina.example.com/index \
    -H "Content-Type: application/json" \
    -d '{"data": [{"text": "first"}, {"text": "second"}, {"text": "third"}]}'
```

Jina Serve is running and serving requests over HTTPS. From here you can:

For the full guide with additional tips, visit the original article on ** Vultr Docs**.
