Packaging Holoscan Applications
The Holoscan App Packager, included as part of the Holoscan CLI as the package command, allows you to package your Holoscan applications into a HAP-compliant container image for distribution and deployment.
Dependencies
Ensure the following are installed in the environment where you want to run the CLI:
- PIP dependencies (automatically installed with the holoscan python wheel) 
- NVIDIA Container Toolkit with Docker - Developer Kits (aarch64): already included in IGX Software and JetPack 
- x86_64: tested with NVIDIA Container Toolkit 1.13.3 w/Docker v24.0.1 
 
- Docker BuildX plugin - Check if it is installed: - $ docker buildx version github.com/docker/buildx v0.10.5 86bdced 
- If not, run the following commands based on the official doc: - # Install Docker dependencies sudo apt-get update sudo apt-get install ca-certificates curl gnupg # Add Docker Official GPG Key sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Configure Docker APT Repository echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker BuildX Plugin sudo apt-get update sudo apt-get install docker-buildx-plugin 
 
- QEMU (Optional) - used for packaging container images of different architectures than the host (example: x86_64 -> arm64) 
 
CLI Installation
The Holoscan CLI is installed as part of the Holoscan SDK and can be called with the following instructions depending on your installation:
- In a virtual environment: the - holoscanCLI should already be in the PATH
- System python: ensure that - $HOME/.local/binis added to your- PATH. If using bash, the following command will make it persist across sessions:- echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc 
Ensure that /opt/nvidia/holoscan/ is added to your PATH. If using bash, the following command will make it persist across sessions:
            
            echo 'alias holoscan=/opt/nvidia/holoscan/bin/holoscan' >> ~/.bashrc
    
If building the SDK from source and starting the build container with run launch, the holoscan CLI should already be in the PATH.
If building bare-metal (advanced), ensure that <BUILD_OR_INSTALL_DIR>/bin is added to your PATH. If using bash, the following command will make it persist across sessions:
            
            echo 'alias holoscan=<BUILD_OR_INSTALL_DIR>/bin/holoscan' >> ~/.bashrc
    
The NGC container has the CLI installed already, no additional steps are required.
The packager feature is also illustrated in the cli_packager and video_replayer_distributed examples.
Additional arguments are required when launching the container to enable the packaging of Holoscan applications inside the NGC Holoscan container. Please see the NGC Holoscan container page for additional details.
- Ensure to use the HAP environment variables wherever possible when accessing data. For example: - Let’s take a look at the distributed video replayer example ( - examples/video_replayer_distributed).- Using the Application Configuration File - In the - mainfunction, we call the- app->config(config_path)function with the default configuration file. The- app->config(...)checks to see if the application was executed with- --configargument first. If- --configwas set, the method uses the configuration file from the- --configargument. Otherwise, it checks if the environment variable- HOLOSCAN_CONFIG_PATHis set and uses that value as the source. If neither were set, the default configuration file (- config_path) is used.- int main(int argc, char** argv) { // Get the yaml configuration file auto config_path = std::filesystem::canonical(argv[0]).parent_path(); config_path /= std::filesystem::path("video_replayer_distributed.yaml"); auto app = holoscan::make_application<DistributedVideoReplayerApp>(); app->config(config_path); app->run(); return 0; } - In the - mainfunction, we call the- app.config(config_file_path)function with the default configuration file. The- app.config(...)method checks to see if the application was executed with- --configargument first. If- --configwas set, the method uses the configuration file from the- --configargument. Otherwise, it checks if the environment variable- HOLOSCAN_CONFIG_PATHis set and uses that value as the source. If neither were set, the default configuration file (- config_file_path) is used.- def main(): input_path = get_input_path() config_file_path = os.path.join(os.path.dirname(__file__), "video_replayer_distributed.yaml") logging.info(f"Reading application configuration from{config_file_path}") app = DistributedVideoReplayerApp(input_path) app.config(config_file_path) app.run() 
- Using Environment Variable - HOLOSCAN_INPUT_PATHfor Data Input- In - Fragment1, we try to set the input video directory with the value defined in- HOLOSCAN_INPUT_PATH. When we instantiate a new Video Stream Replayer operator, we pass in all configuration values from the- from_config("replayer")call. In addition, we include- argsthat we created with the value from- HOLOSCAN_INPUT_PATHif available as the last argument to override the- directorysetting.- class Fragment1 : public holoscan::Fragment { public: void compose() override { using namespace holoscan; ArgList args; auto data_directory = std::getenv("HOLOSCAN_INPUT_PATH"); if (data_directory != nullptr && data_directory[0] != '\0') { auto video_directory = std::filesystem::path(data_directory); video_directory /= "racerx"; args.add(Arg("directory", video_directory.string())); HOLOSCAN_LOG_INFO("Using video from {}", video_directory.string()); } auto replayer = make_operator<ops::VideoStreamReplayerOp>("replayer", from_config("replayer"), args); add_operator(replayer); } }; - In - Fragment1, we try to set the input video directory with the value defined in- HOLOSCAN_INPUT_PATH. When we instantiate a new Video Stream Replayer operator, we pass in the- video_pathalong with all- replayerconfigurations found in the configuration file.- class Fragment1(Fragment): def __init__(self, app, name): super().__init__(app, name) def __init__(self, app, name): super().__init__(app, name) def compose(self): # Set the video source video_path = self._get_input_path() logging.info( f"Using video from{video_path}" ) # Define the replayer and holoviz operators replayer = VideoStreamReplayerOp( self, name="replayer", directory=video_path, **self.kwargs("replayer") ) self.add_operator(replayer) def _get_input_path(self): path = os.environ.get( "HOLOSCAN_INPUT_PATH", os.path.join(os.path.dirname(__file__), "data") ) return os.path.join(path, "racerx") 
 
- Include a YAML configuration file as described in the Application Runner Configuration page. 
- Use the - holoscan packagecommand to create a HAP container image. For example:- holoscan package --platform x64-workstation --tag my-awesome-app --config /path/to/my/awesome/application/config.yaml /path/to/my/awesome/application/ 
Common Issues When Using Holoscan Packager
DNS Name Resolution Error
The Holoscan Packager may be unable to resolve hostnames in specific networking environments and may show errors similar to the following:
            
            curl: (6) Could not resolve host: github.com.
Failed to establish a new connection:: [Errno -3] Temporary failure in name solution...
    
To resolve these errors, edit the /etc/docker/daemon.json file to include dns and dns-serach fields as follows:
            
            {
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "args": [],
            "path": "nvidia-container-runtime"
        }
    },
    "dns": ["IP-1", "IP-n"],
    "dns-search": ["DNS-SERVER-1", "DNS-SERVER-n"]
}
    
You may need to consult your IT team and replace IP-x and DNS-SERVER-x with the provided values.
The packaged Holoscan application container image can run with the Holoscan App Runner:
            
            holoscan run -i /path/to/my/input -o /path/to/application/generated/output my-application:1.0.1
    
Since the packaged Holoscan application container images are OCI-compliant, they’re also compatible with Docker, Kubernetes, and containerd.
Each packaged Holoscan application container image includes tools inside for extracting the embedded application, manifest files, models, etc. To access the tool and to view all available options, run the following:
            
            docker run -it my-container-image[:tag] help
    
The command should prints following:
            
            USAGE: /var/holoscan/tools [command] [arguments]...
 Command List
    extract  ---------------------------  Extract data based on mounted volume paths.
        /var/run/holoscan/export/app        extract the application
        /var/run/holoscan/export/config     extract app.json and pkg.json manifest files and application YAML.
        /var/run/holoscan/export/models     extract models
        /var/run/holoscan/export/docs       extract documentation files
        /var/run/holoscan/export            extract all of the above
        IMPORTANT: ensure the directory to be mounted for data extraction is created first on the host system.
                   and has the correct permissions. If the directory had been created by the container previously
                   with the user and group being root, please delete it and manually create it again.
    show  -----------------------------  Print manifest file(s): [app|pkg] to the terminal.
        app                                 print app.json
        pkg                                 print pkg.json
    env  -------------------------  Print all environment variables to the terminal.
    
The tools can also be accessed inside the Docker container via /var/holoscan/tools.
For example, run the following commands to extract the manifest files and the application configuration file:
            
            # create a directory on the host system first
mkdir -p config-files
# mount the directory created to /var/run/holoscan/export/config
docker run -it --rm -v $(pwd)/config-files:/var/run/holoscan/export/config my-container-image[:tag] extract
# include -u 1000 if the above command reports a permission error
docker run -it --rm -u 1000 -v $(pwd)/config-files:/var/run/holoscan/export/config my-container-image[:tag] extract
# If the permission error continues to occur, please check if the mounted directory has the correct permission.
# If it doesn't, please recreate it or change the permissions as needed.
# list files extracted
ls config-files/
# output:
# app.json app.yaml pkg.json