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
.neoappapp packageImport 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 matchspec.imageinapp.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 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://<device-ip>:8080Navigate 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
.neoappapp package (or a bare image tar); upload progress is displayedImage 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.
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 |
No |
Contact email |
Resource Limits (spec.resources)
Field |
Default |
Description |
|---|---|---|
cpu |
— |
CPU limit, e.g., |
memory |
— |
Memory limit, e.g., |
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 videocam0_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