Skip to content

The echo-multi app

Several examples assume you have a Nextmv application called echo-multi. This is just a simple application created for demonstration purposes. It takes the input files and echoes them as output files.

Let's get set up with the echo-multi application. Before starting:

  1. Sign up for a Nextmv account.
  2. Get your API key. Go to Settings > API Key.

Make sure that you have your API key set as an environment variable:

export NEXTMV_API_KEY="<YOUR-API-KEY>"

Now that you have a valid Nextmv account and API key, let's create the echo-multi Nextmv app (start in an empty directory).

First let's create a uv project.

uv init --bare
Initialized project `echo-multi`

Create a folder inputs/ and add some sample input files to it. For example, you can create two text files input.csv and input.txt with some sample content.

id,name,value
jumping,jack,10
running,jill,20
I have a rabbit and a hat.

Create a file called main.py with the code for the basic app that echoes the input.

main.py
import glob
import os
import time

import nextmv


def main():
    manifest = nextmv.Manifest.from_yaml()
    options = manifest.extract_options()

    # Read and prepare the input data.
    input_data = read_input(
        input_path=manifest.configuration.content.multi_file.input.path
    )

    # Log information about the input files.
    nextmv.log(f"Size of input files (count: {len(input_data)}):")
    for file_path, content in input_data.items():
        nextmv.log(f"  {file_path}: {len(content)} bytes")

    # Sleep for the specified duration.
    nextmv.log(f"Sleeping for {options.duration} seconds...")
    time.sleep(options.duration)
    nextmv.log("Woke up from sleep.")

    # Write the output.
    write_output(
        output_path=manifest.configuration.content.multi_file.output.solutions,
        content=input_data,
    )


def read_input(input_path: str) -> dict[str, bytes]:
    """Reads the input files to memory."""
    input_files = glob.glob(os.path.join(input_path, "**/*"), recursive=True)
    content = {}
    for file_path in input_files:
        if os.path.isfile(file_path):
            with open(file_path, "rb") as file:
                nextmv.log(f"Reading file: {file_path}")
                content[file_path] = file.read()

    return content


def write_output(output_path: str, content: dict[str, bytes]) -> None:
    """Writes the given output files."""
    if not os.path.exists(output_path):
        os.makedirs(output_path)

    for file_path, data in content.items():
        output_file_path = os.path.join(output_path, os.path.basename(file_path))
        with open(output_file_path, "wb") as file:
            nextmv.log(f"Writing file: {output_file_path}")
            file.write(data)


if __name__ == "__main__":
    main()

Note that the application uses the nextmv Python SDK. This library is a dependency of Nextpipe and should be installed automatically when you install Nextpipe. For this simple example, let's add the nextmv dependency so that we can run the app locally.

uv add nextmv

Now, add an app.yaml manifest that configures this as a multi-file application and adds a duration option.

app.yaml
type: python
runtime: ghcr.io/nextmv-io/runtime/python:3.11
python:
  pip-requirements: pyproject.toml
files:
  - main.py
configuration:
  content:
    format: multi-file
    multi-file:
      input:
        path: inputs
      output:
        solutions: outputs
  options:
    strict: false
    items:
      - name: duration
        option_type: float
        default: 1.0
        description: "Runtime duration (in seconds)."
        required: false

At this point, your project directory should look like this:

.
├── app.yaml
├── inputs
   ├── input.csv
   └── input.txt
├── main.py
├── pyproject.toml
└── uv.lock

We can now run the simple application locally to test it. This app will read the input files from the specified inputs/ directory, sleep for the specified duration, and then write the output files to the specified outputs directory.

uv run main.py
Reading file: inputs/input.csv
Reading file: inputs/input.txt
Size of input files (count: 2):
  inputs/input.csv: 45 bytes
  inputs/input.txt: 26 bytes
Sleeping for 1.0 seconds...
Woke up from sleep.
Writing file: outputs/input.csv
Writing file: outputs/input.txt

An outputs directory should have been created with the same files as in the inputs directory.

The last step is to push the application to Nextmv Cloud. We can do this with the CLI. Before pushing, create an application with the ID echo-multi.

nextmv cloud app create --app-id echo-multi
 Creating application...
{
  "id": "echo-multi",
  "name": "echo-multi",
  "description": "",
  "type": "custom",
  "default_instance": "",
  "default_experiment_instance": "",
  "subscription_id": "",
  "locked": false,
  "created_at": "2026-07-29T16:58:42.135197Z",
  "updated_at": "2026-07-29T16:58:42.135197Z"
}

Lastly, push the application to your Nextmv account.

nextmv cloud app push --app-id echo-multi
💿 Starting build for Nextmv application echo-multi.
🐍 Bundling Python dependencies.
     33/34 compressed dependencies found in cache.
    🐇 Downloading and compressing 1 dependency from package index.
📋 Copying files listed in app.yaml manifest.
💾 Compressing application into tarball.
    🛠  Appending application files.
📦 Packaged application (2 app files, 51.27 MiB with dependencies).
🌟 Pushing to application: echo-multi.
💥 Successfully pushed to application: echo-multi.
{
  "app_id": "echo-multi",
  "endpoint": "https://api.cloud.nextmv.io",
  "instance_url": "v1/applications/echo-multi/runs?instance_id=latest"
}

🎉🎉🎉 Congratulations, you are ready to run the examples!