Multiple services

DipDup provides several ways to run multiple services as a part of a single application. This is useful for running background tasks, heavy computations, or any other long-running processes that should not block the main DipDup service.

This table summarizes the available methods:

methoddescriptionprocessdatabase
Job schedulerAdd definition to jobs config sectionsame processsame database
CLI commandImplement a CLI command to extend dipdup toolseparate processsame or separate database
Isolated packageCreate a separate package with its own dipdup configseparate processseparate database

Job scheduler

DipDup has a built-in job scheduler that allows you to run tasks on schedule or in background.

Here's a minimal configuration for long-running service:

dipdup.yaml
hooks:
  service:
    callback: service

jobs:
  service_daemon:
    hook: service
    daemon: true

Run dipdup init after adding this configuration to create a callback stub in hooks directory:

DipDup jobs are executed in the same process, thread and asyncio event loop as the main DipDup service, and use the same database connection. You can use ctx to communicate with indexer and access its state.

CLI command

Another approach is to implement a custom CLI command that runs in a separate process, but still uses the same DipDup configuration and environment variables. This is useful for running heavy computations or background tasks that do not need to be part of the main indexing process.

First, create a new file cli.py in the project root directory:

cli.py
from contextlib import AsyncExitStack

import click
from dipdup.cli import cli, _cli_wrapper
from dipdup.config import DipDupConfig
from dipdup.context import DipDupContext
from dipdup.utils.database import tortoise_wrapper


@cli.command(help='Run heavy calculations')
@click.option('--key', help='Example option', required=True)
@click.pass_context
@_cli_wrapper
async def heavy_stuff(ctx, key: str) -> None:
    config: DipDupConfig = ctx.obj.config

    async with tortoise_wrapper(
        url=config.database.connection_string,
        models=f'{config.package}.models',
    ):
        ...


if __name__ == '__main__':
    cli(prog_name='dipdup', standalone_mode=True)

Then use python -m dipdup_indexer.cli instead of dipdup as an entrypoint. Now you can call heavy-stuff like one of DipDup commands. dipdup.cli:cli group handles arguments and config parsing, graceful shutdown, and other boilerplate.

Terminal
python -m dipdup_indexer.cli heavy-stuff --key value

If your service is long-running, you need to update Dockerfile and Compose manifests. Copy deploy/Dockerfile to deploy/Dockerfile.service and add the following lines to the end of the file:

deploy/Dockerfile.service
ENTRYPOINT ["python", "-m", "dipdup_indexer.cli"]
CMD ["heavy-stuff", "--key", "value"]

For Docker Compose, add a new service to deploy/compose.yaml:

docker-compose.yml
services:
  dipdup_service:
    build:
      context: .
      dockerfile: deploy/Dockerfile.service
    ...

Run make up to update the stack.

Isolated package

You can also create a separate package with its own dipdup.yaml configuration file and reuse only the necessary parts of the project. Useful for monorepos with multiple DipDup projects.

Warning
Do not share the same database between multiple DipDup projects! It will lead to schema conflicts and data corruption.

New project directory can be either a sibling of the main project or a subdirectory. In this example, we will create a nested package dipdup_indexer/service

Terminal
# Create a new package with its own DipDup config
dipdup new --name service

# Move to the service directory
cd service
make install
source .venv/bin/activate
Help and tips -> Join our Discord
Ideas or suggestions -> Issue Tracker
GraphQL IDE -> Open Playground