Docker Compose Support#

One of the simplest ways to prototype or serve in production is to run your Flow with docker-compose.

A Flow is composed of Executors which run Python code that operates on DocumentArray. These Executors live in different runtimes depending on how you want to deploy your Flow.

By default, if you are serving your Flow locally they live within processes. Nevertheless, because Jina is cloud native your Flow can easily manage Executors that live in containers and that are orchestrated by your favorite tools. One of the simplest is Docker Compose which is supported out of the box.

You can deploy a Flow with Docker Compose in one line:

from jina import Flow
flow = Flow(...).add(...).add(...)
flow.to_docker_compose_yaml('docker-compose.yml')

Jina generates a docker-compose.yml configuration file corresponding with your Flow. You can use this directly with Docker Compose, avoiding the overhead of manually defining all of your Flow’s services.

Use Docker-based Executors

All Executors in the Flow should be used with jinahub+docker://... or docker://....

Health check available from 3.1.3

If you use Executors that rely on Docker images built with a version of Jina prior to 3.1.3, remove the health check from the dumped YAML file, otherwise your Docker Compose services will always be “unhealthy.”

Matching Jina versions

If you change the Docker images in your Docker Compose generated file, ensure that all services included in the Gateway are built with the same Jina version to guarantee compatibility.

Example: Index and search images using CLIPEncoder and ANNLiteIndexer#

Install Docker Compose locally to follow this how-to.

This example shows how to build and deploy a Flow with Docker Compose, using CLIPImageEncoder as an image encoder and ANNLiteIndexer as an indexer to perform fast nearest neighbor retrieval on image embeddings.

Deploy the Flow#

First define the Flow and generate the Docker Compose YAML configuration:

In a flow.yml file :

jtype: Flow
with:
  port: 8080
  protocol: http
executors:
- name: encoder
  uses: jinahub+docker://CLIPEncoder
  replicas: 2
- name: indexer
  uses: jinahub+docker://AnnLiteIndexer
  uses_with:
    dim: 512
  shards: 2

Then in a shell run:

jina export docker-compose flow.yml docker-compose.yml 

In python run

from jina import Flow

flow = (
    Flow(port=8080, protocol='http')
    .add(name='encoder', uses='jinahub+docker://CLIPEncoder', replicas=2)
    .add(
        name='indexer',
        uses='jinahub+docker://AnnLiteIndexer',
        uses_with={'dim': 512},
        shards=2,
    )
)
flow.to_docker_compose_yaml('docker-compose.yml')

Hint

You can use a custom jina Docker image for the Gateway service by setting the envrironment variable JINA_GATEWAY_IMAGE to the desired image before generating the configuration.

let’s take a look at the generated compose file:

version: '3.3'
...
services:
  encoder-rep-0:  # # # # # # # # # # #          
                  #     Encoder       #
  encoder-rep-1:  # # # # # # # # # # #

  indexer-head:   # # # # # # # # # # #          
                  #                   #
  indexer-0:      #     Indexer       #
                  #                   #
  indexer-1:      # # # # # # # # # # #

  gateway: 
    ...
    ports:
    - 8080:8080

Tip

The default compose file generated by the Flow contains no special configuration or settings. You may want to adapt it to your own needs.

You can see that six services are created:

  • 1 for the Gateway which is the entrypoint of the Flow.

  • 2 associated with the encoder for the two Replicas.

  • 3 associated with the indexer, one for the Head and two for the Shards.

Now, you can deploy this Flow :

docker-compose -f docker-compose.yml up

Query the Flow#

Once we see that all the services in the Flow are ready, we can send index and search requests.

First define a client:

from jina.clients import Client

client = Client(host='http://localhost:8080')

Then index the set of images we want to search:

from docarray import DocumentArray

da = DocumentArray.pull('demo-da-images-jina', show_progress=True)

da_query = da[0:1]  # one document for query
da_index = da[1:]  # the rest is for indexing

indexed_docs = client.index(inputs=da_index)
print(f'Indexed Documents: {len(indexed_docs)}')
Indexed Documents: 99

We indexed 99 Documents!

Now we can search for the closest image to the query image:

queried_docs = client.search(inputs=da_query)

matches = queried_docs[0].matches
print(f'Matched Documents: {len(matches)}')
Matched Documents: 10

This gives us the list of the ten closest images to the query.