Application Image Build and Import Guide

Overview

This guide describes how to build an application Docker image in your development environment, package it as a .neoapp app package, and import it onto an NeoRuntime device for deployment.

The complete workflow includes:

  1. Prepare application files (Dockerfile, app.yaml, app.py)

  2. Build the Docker image

  3. Export the image and package it as a .neoapp app package

  4. Import via the Web Console installation wizard, or use the command line

Tip

The deliverable is a single-file .neoapp app package (tar.gz holding app.yaml and image.tar). Uploading one .neoapp in the Web Console completes the install — the server unpacks the manifest and image automatically; a bare image tar can also be uploaded, with its configuration generated by the wizard form.

Step 1: Prepare Application Files

Create an application directory and prepare the core files below. app.yaml ships inside the .neoapp package alongside the image and can still be fine-tuned in the installation wizard; only a bare-image upload relies entirely on the wizard form for configuration.

Create Application Directory

mkdir my-app && cd my-app

Application Code (app.py)

#!/usr/bin/env python3
"""My Application"""

import signal
from neoruntime_ipc_sdk import InferenceClient, EventClient, DeviceClient, Config


class MyApp:
    def __init__(self):
        self.running = True
        self.app_id = Config.get_app_id()

        self.inference = InferenceClient()
        self.events = EventClient()
        self.device = DeviceClient()

        signal.signal(signal.SIGINT, self.signal_handler)
        signal.signal(signal.SIGTERM, self.signal_handler)

    def signal_handler(self, signum, frame):
        self.running = False

    def run(self):
        try:
            for frame, result in self.inference.subscribe(
                stream="main", model="person_v1", fps=10
            ):
                if not self.running:
                    break
                person_count = result.count_by_label("person")
                if person_count > 0:
                    self.events.publish(f"app/{self.app_id}/detection", {
                        "count": person_count,
                    })
        except Exception as e:
            print(f"[{self.app_id}] Error: {e}")
        finally:
            self.inference.close()
            self.events.close()
            self.device.close()


if __name__ == "__main__":
    MyApp().run()

Dockerfile

FROM python:3.9-slim

LABEL maintainer="your@email.com"

# Install the NeoRuntime Python SDK (published on PyPI; the pinned
# version keeps builds reproducible — bump it alongside SDK upgrades)
RUN python -m pip install --no-cache-dir \
    "neoruntime-ipc-sdk==0.7.4"

WORKDIR /app
COPY app.py app.yaml /app/

RUN mkdir -p /app/logs /app/data

# Non-root user (recommended)
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

ENV APP_ID=my_app
ENV DEBUG=0
ENV LOG_LEVEL=INFO

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD python3 -c "import sys; sys.exit(0)"

CMD ["python3", "app.py"]

Application Manifest (app.yaml)

A minimal working app.yaml (see app_yaml_reference for all fields):

apiVersion: v1
kind: Application

metadata:
  id: my_app
  name: My Application
  version: 1.0.0
  description: Person detection demo

spec:
  image: my-app:1.0.0
  permissions:
    video:
      - cam0_main.raw
    inference:
      models: ["person_v1"]

Note

The official app repository neoruntime-apps provides a full manifest template in templates/basic/; its example and showcase directories are good references too.

Step 2: Build the Docker Image

Run the build command in the application directory:

docker build -t my-app:1.0.0 .

Build parameters:

  • -t my-app:1.0.0 — Image name and tag, must match spec.image in app.yaml

  • . — Build context is the current directory

Verify the image was built successfully:

docker images | grep my-app

Note

If the application requires additional dependencies, add a requirements.txt file to the directory and include RUN pip install -r requirements.txt in the Dockerfile. For offline or repeatable builds, build the SDK wheel first, copy neoruntime_ipc_sdk-*.whl into the image, and install that local wheel instead of installing from GitHub. Source installs can pin a release tag instead: git+https://github.com/camthink-ai/neoruntime-sdks.git@v0.7.4#subdirectory=python.

Step 3: Export the Image and Package a .neoapp

Export the built image as a tar file (named image.tar inside the package):

docker save my-app:1.0.0 -o image.tar

Then assemble app.yaml and image.tar into a .neoapp app package:

PKG=my-app-1.0.0-arm64
mkdir -p "$PKG"
cp app.yaml "$PKG/"
mv image.tar "$PKG/"
(cd "$PKG" && sha256sum app.yaml image.tar > SHA256SUMS)
tar -czf "$PKG.neoapp" "$PKG"

A .neoapp is just tar.gz: it must contain app.yaml and image.tar (at the root or inside a single directory — both work); extras like SHA256SUMS are ignored by the installer. The gzip layer doubles as transfer compression, so no separate compression step is needed.

Note

The official app repository neoruntime-apps ships scripts/build_app.sh <app-dir> which runs docker build → save → .neoapp packaging in one shot; its Releases also offer prebuilt showcase packages (*-arm64.neoapp) ready to download and import.

Step 5: Command Line Import (Alternative)

If you cannot use the Web Console, transfer the .neoapp app package to the device via SCP, unpack it, and install with aipc-cli.

Transfer the package to the device:

scp my-app-1.0.0-arm64.neoapp root@<device-ip>:/tmp/

Then SSH into the device:

# Unpack the .neoapp (yields app.yaml and image.tar)
tar xzf /tmp/my-app-1.0.0-arm64.neoapp -C /tmp/

# Install the application (positional args: manifest first, image tar second)
aipc-cli app install /tmp/my-app-1.0.0-arm64/app.yaml \
                     /tmp/my-app-1.0.0-arm64/image.tar

# Start the application
aipc-cli app start my_app

# List application status
aipc-cli app list

# View application logs
aipc-cli app logs my_app

You can also use a gRPC client to directly call the app-manager service:

grpcurl -plaintext \
  -d '{"manifest_path": "/tmp/my-app-1.0.0-arm64/app.yaml",
       "image_path": "/tmp/my-app-1.0.0-arm64/image.tar"}' \
  unix:///run/aipc/app-manager.sock \
  appmanager.AppManager/InstallApp

Application Manifest Reference (app.yaml)

Below is a detailed description of each field in app.yaml.

Metadata

Field

Required

Description

id

Yes

Unique application identifier (lowercase letters, numbers, underscores)

name

Yes

Application display name

version

Yes

Semantic version number (e.g., 1.0.0)

description

Yes

Application description

author

No

Author name

email

No

Contact email

Resource Limits (spec.resources)

Field

Default

Description

cpu

CPU limit, e.g., "50%" or "0.5"

memory

Memory limit, e.g., "256Mi" or "1Gi"

shm

false

Enable shared memory (required for zero-copy video streams)

Permission Configuration (spec.permissions)

Video Stream Permissions (video)

Specify video streams the application can access:

  • cam0_main.raw — Raw video stream (via SHM zero-copy)

  • cam0_sub.raw — Sub-stream raw video

  • cam0_main — Encoded video stream (via Unix socket)

Note

Permission-layer and SDK stream naming live at different layers: manifest permissions use the platform-side names (e.g. cam0_main.raw); SDK calls (FdMediaClient.subscribe / InferenceClient.subscribe) subscribe by the device-exposed stream IDs main / sub.

AI Inference Permissions (inference)

Field

Default

Description

models

[]

List of usable models

max_qps

Maximum QPS limit

max_concurrent

Maximum concurrent inferences

Event Bus Permissions (events)

  • publish — Publishable event topics (supports wildcards *)

  • subscribe — Subscribable event topics (supports wildcards *)

Device Control Permissions (device)

Field

Default

Description

light

false

Fill light control

ir_cut

false

IR cut filter control

ptz

false

PTZ control

lens

false

Lens zoom/focus control

Network Permissions (network)

  • mode — Network mode: "isolated" (default) or "host"

  • outbound — Allowed outbound addresses (in isolated mode)

Lifecycle Configuration

Field

Default

Description

autostart

false

Automatically start on system boot

restart_policy

“no”

Restart policy: always / on-failure / no

restart_max_retries

3

Maximum restart attempts (on on-failure)

Health Check

Field

Default

Description

enabled

false

Enable health check

interval

30s

Check interval

timeout

5s

Timeout duration

retries

3

Failure retry count

FAQ

Image Build Failure

Error: failed to solve: failed to fetch

Check network connectivity. If a proxy is needed:

docker build --build-arg HTTP_PROXY=http://proxy:port \
             --build-arg HTTPS_PROXY=http://proxy:port \
             -t my-app:1.0.0 .

Package Too Large

A .neoapp is already gzip-compressed. If it is still too large, check whether the image carries unnecessary layers or caches (install dependencies with --no-cache-dir, use multi-stage builds, etc.):

# Inspect layer sizes to find the big ones
docker history my-app:1.0.0

Import Failure

Error: Failed to import image to containerd

# Check containerd status
systemctl status containerd

# Manual import test
ctr -n aipc images import /tmp/my-app-1.0.0-arm64/image.tar

Permission Error

Error: Permission denied

chmod 644 /tmp/my-app-1.0.0-arm64/app.yaml /tmp/my-app-1.0.0-arm64/image.tar