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: #. Prepare application files (Dockerfile, app.yaml, app.py) #. Build the Docker image #. Export the image and package it as a ``.neoapp`` app package #. 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. .. _app_image_step1: 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash mkdir my-app && cd my-app Application Code (app.py) ~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python #!/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 ~~~~~~~~~~ .. code-block:: 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): .. code-block:: yaml 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. .. _app_image_step2: Step 2: Build the Docker Image ------------------------------- Run the build command in the application directory: .. code-block:: bash 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: .. code-block:: bash 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``. .. _app_image_step3: Step 3: Export the Image and Package a .neoapp ----------------------------------------------- Export the built image as a tar file (named ``image.tar`` inside the package): .. code-block:: bash docker save my-app:1.0.0 -o image.tar Then assemble ``app.yaml`` and ``image.tar`` into a ``.neoapp`` app package: .. code-block:: bash 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 `` which runs docker build → save → ``.neoapp`` packaging in one shot; its Releases also offer prebuilt showcase packages (``*-arm64.neoapp``) ready to download and import. .. _app_image_step4: Step 4: Web Console Import (Recommended) ----------------------------------------- The Web Console **Import Application** dialog has three screens: choose source → configure the app → install progress. .. note:: The single upload slot accepts: a ``.neoapp`` app package (recommended — the server unpacks ``app.yaml`` and the image automatically), or a bare image ``.tar`` / ``.tar.gz`` / ``.tgz``, up to 2GB. Open the Import Dialog ~~~~~~~~~~~~~~~~~~~~~~ #. Open a browser and navigate to the device Web Console: ``http://:8080`` #. Navigate to the **Application Management** page #. Click the **Import Application** card to open the dialog Screen 1 — Choose Source ~~~~~~~~~~~~~~~~~~~~~~~~ - **Local upload** (default; recommended for offline devices): drag in or select a ``.neoapp`` app package (or a bare image tar); upload progress is displayed - **Image Registry**: enter the Docker image address (e.g., ``docker.io/library/nginx:latest``); the device needs network access After a ``.neoapp`` upload, the server unpacks the embedded ``app.yaml`` and image; the following form treats the packaged ``app.yaml`` as the source of truth and can be fine-tuned. With a bare image tar, the configuration comes entirely from the form. Screen 2 — Configure the Application ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A single-page form organized in sections (sidebar section navigation, with a **form/YAML** dual-view switch): - **Basic Info**: application ID, name, version, description - **Resources**: CPU / memory limits, shared memory (zero-copy video streams), auto start, restart policy - **Models**: inference models available on the device, max QPS - **Permissions**: video streams, event topics (wildcards supported), network mode, device control - **Advanced** (optional): environment variables, volume mounts Screen 3 — Install Progress ~~~~~~~~~~~~~~~~~~~~~~~~~~~ After submission the install task progress is displayed; the application appears in the application list once installation completes. .. _app_image_step5: 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: .. code-block:: bash scp my-app-1.0.0-arm64.neoapp root@:/tmp/ Then SSH into the device: .. code-block:: bash # 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: .. code-block:: bash 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 .. _app_yaml_reference: Application Manifest Reference (app.yaml) ------------------------------------------ Below is a detailed description of each field in ``app.yaml``. Metadata ~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 20 10 70 * - 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) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 15 15 70 * - 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)** .. list-table:: :header-rows: 1 :widths: 20 15 65 * - 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)** .. list-table:: :header-rows: 1 :widths: 15 15 70 * - 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 ~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 25 15 60 * - 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 ~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 15 15 70 * - Field - Default - Description * - enabled - false - Enable health check * - interval - 30s - Check interval * - timeout - 5s - Timeout duration * - retries - 3 - Failure retry count .. _app_image_faq: FAQ --- Image Build Failure ~~~~~~~~~~~~~~~~~~~ **Error**: ``failed to solve: failed to fetch`` Check network connectivity. If a proxy is needed: .. code-block:: bash 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.): .. code-block:: bash # Inspect layer sizes to find the big ones docker history my-app:1.0.0 Import Failure ~~~~~~~~~~~~~~ **Error**: ``Failed to import image to containerd`` .. code-block:: bash # 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`` .. code-block:: bash chmod 644 /tmp/my-app-1.0.0-arm64/app.yaml /tmp/my-app-1.0.0-arm64/image.tar