Skip to main content

StratusCore application manager. Requires Docker and Docker Compose.

Project description

scapps: The StratusCore Application Manager

The StratusCore Apps CLI tool for creating and running applications.

Requirements

  • Docker with the Compose plugin (docker compose)

Quick Start

# create a new application
scapps new my-app

# run the application
scapps run my-app

# run all apps in a directory
scapps run apps/managed

# check running applications
scapps info

# attach to application logs
scapps attach my-app

# stop the application
scapps down my-app

# stop all running scapps apps
scapps down

# build/pull images and package into a tar
scapps configure my-app

Template Option Flags

When creating a new app with scapps new, you can use template option flags to scaffold an application for a commonly used application type. These flags populate your application with pre-configured directories, Docker configurations, and scapps.yaml files needed for that application type.

In the generated scapps.yaml, the template option flags can create entries in the default service section for runtime features. These features provide additional Docker compose configuration at runtime for the specified application type. For example, using the --ros2 template option flag adds the ros2: true runtime feature to the scapps.yaml, which does the following when the application is ran:

  • share memory with the host
  • share network with the host
  • set the container to privileged
  • set ROS_DOMAIN_ID
  • set FASTRTPS_DEAFULT_PROFILE_FILE
  • attach dds_profile.xml volume

The corresponding scapps.yaml and compose.yaml (called compose.<app-name>.yaml and generated by scapps run) files for a --ros2 template option flag application are shown below.

scapps.yaml

name: my-ros2-app
services:
  ros2-node:
    build:
      context: .
      dockerfile: Dockerfile
      network: host

    ros2: true

compose.my-ros2-app.yaml

# generated when `scapps run` is called
name: my-ros2-app
services:
  ros2-node:
    # copied over from scapps.yaml
    build:
      context: .
      dockerfile: Dockerfile
      network: host

    # generated by ros2:true runtime feature
    ipc: host
    network_mode: host
    privileged: true
    environment:
    - ROS_DOMAIN_ID=0
    - FASTRTPS_DEFAULT_PROFILES_FILE=/dds_profile.xml
    volumes:
    - ./dds_profile.xml:/dds_profile.xml:ro

Available Flags

Flag Template Used Adds Runtime Feature Description
--ros2 ROS2Template ros2: true creates ROS2 workspace, ROS2 Dockerfile, and adds ROS2 runtime feature

Available Runtime Features

These runtime features can be added using a template options flag in scapps new or manually added to the scapps.yaml after creation. The entries listed below show the format of the runtime feature inside of scapps.yaml. For example, the ros2: true runtime feature can be seen in context above in the scapps.yaml excerpt.

Runtime Feature Description
ros2: true enables ROS2 networking with host, shared memory, ROS_DOMAIN_ID, and the DDS profile configuration
nvidia-gpu: true enables NVIDIA GPU device access, driver exposure, and GPU environment configuration
ui: {port: <port>[, path: <path>]} registers the frontend or backend service with the Traefik reverse proxy systemmd service, making it accessible on the host at /<path>

Note: The ui runtime feature takes a configuration dict rather than a boolean. port is required and specifies the service's port in the container. path is optional and defaults to the app name; it defines the URL path prefix to where the frontend or backend of your UI is served.

Docker Compose Additions

When a runtime feature is added, the generated compose.<app-name>.yaml file used by Docker is modified with the following for each specified feature:

ROS2 Runtime Feature

scapps.yaml

# added to the service definition
ros2: true

compose.<app-name>.yaml

# added to the service definition
network_mode: host
ipc: host
privileged: true
environment:
  - ROS_DOMAIN_ID=0
  - FASTRTPS_DEFAULT_PROFILES_FILE=/dds_profile.xml
volumes:
  - ./dds_profile.xml:/dds_profile.xml:ro

NVIDIA GPU Runtime Feature

scapps.yaml

# added to the service definition
nvidia-gpu: true

compose.<app-name>.yaml

# added to the service definition
deploy:
  resources:
    reservations:
      devices:
        - driver: nvidia
          count: all
          capabilities: [gpu]
environment:
  - NVIDIA_VISIBLE_DEVICES=all
  - NVIDIA_DRIVER_CAPABILITIES=all

UI Runtime Feature

scapps.yaml

# added to the service definition
ui:
  - port
  - path # optional

compose.<app-name>.yaml

# added to the service definition
  networks:
    - reverse-proxy-network
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.<app-name>-<service-name>.rule=PathPrefix(`/<path>`)"
    - "traefik.http.routers.<app-name>-<service-name>.middlewares=<app-name>-<service-name>-strip@docker"
    - "traefik.http.middlewares.<app-name>-<service-name>-strip.stripprefix.prefixes=/<path>"
    - "traefik.http.services.<app-name>-<service-name>.loadbalancer.server.port=<port>"

# added to the top-level networks section
networks:
  reverse-proxy-network:
    name: reverse-proxy-network
    external: true

Where <path> defaults to the app name if not specified in the ui config block.

App Dependencies

An application can declare other StratusCore apps it depends on by adding a top-level dependencies key to its scapps.yaml. Each entry is the app name (the name field in the target app's scapps.yaml), not its directory name.

name: my-app
dependencies:
  - my-app-dependency
services:
  my-service:
    build:
      context: .
      dockerfile: Dockerfile

Behavior

scapps run

Before starting an app, scapps run checks whether every declared dependency is already running. For each dependency that is not running:

  1. It searches only within the same parent folder (sibling apps) for the dependency.
  2. If found in the same folder, it automatically starts the dependency in detached mode first.
  3. If the dependency is not in the same folder, it will not be auto-started. scapps run searches the broader directory tree, shows the location with a manual start hint, and exits with an error.
  4. If the dependency cannot be found anywhere on disk, an error is shown and the command exits.

In all cases where a dependency is not running and is outside the same directory, the app will not start.

scapps info --verbose

The verbose output shows a Dependencies section for each app that declares dependencies, with a live ✓/✗ indicator showing whether each dependency is currently running.

Commands

scapps new <app-dir> [options]

Create a new application from a template with the specified directory structure and configuration files.

The new command creates:

  • Dockerfile and entrypoint.sh
  • scapps.yaml configuration file (with runtime feature flags if template enables them)
  • Application directory structure
  • Template-specific files (e.g., DDS profiles for ROS2)

Options:

  • --ros2 - Use ROS2 template (creates workspace, DDS profile, ROS2 Dockerfile, and adds ROS2 runtime feature)

Examples:

scapps new my-app                             # basic Ubuntu image app
scapps new my-ros-app --ros2                  # ROS2 app

scapps run <app-dir> [options]

Generate a Docker compose file from the scapps.yaml configuration and run the application containers. This command reads runtime feature flags from scapps.yaml, applies the appropriate runtime feature configurations, generates a compose file, and starts the containers.

The run command automatically builds images if they don't exist, making it the primary command for starting your application.

Pass a single app directory, multiple app directories, or a parent directory containing multiple apps. When multiple apps are resolved, they are started in order; if any app fails to start, all previously started apps (and the failed one) are stopped automatically.

If the scapps.yaml contains a dependencies list, run checks each dependency before starting the app. Any dependency that is not already running will be located on disk and started automatically in detached mode if in the same directory. If a dependency cannot be found, the command exits with an error.

Options:

  • -d, --detach - Run containers in detached mode (background). This is the default behavior.
  • -a, --attach - Attach to container output to see logs in real-time.
  • -r, --rebuild - Force rebuild of images before starting containers.
  • --pause-on-failure - When an app fails to start while running multiple at once, pause for inspection before stopping all started apps.

Examples:

scapps run my-app                    # run single app in background
scapps run my-app -a                 # run with output
scapps run my-app -r                 # rebuild and run
scapps run apps/managed              # run all apps in a directory
scapps run app1 app2                 # run multiple specific apps
scapps run apps/managed --pause-on-failure  # pause before teardown on failure

scapps down [app-name-or-dir ...]

Stop and remove containers for one or more applications. Accepts app names, app directory paths, or a parent directory containing multiple apps. Omit all arguments to stop every running scapps-managed app.

Containers and networks are removed, but volumes and images are preserved.

Arguments:

  • [app-name-or-dir ...] - Zero or more project names, app directory paths, or a parent directory. Omit to stop all running scapps apps.

Examples:

scapps down                     # stop all running scapps apps
scapps down my-app              # stop by name
scapps down /path/to/my-app     # stop by path
scapps down apps/managed        # stop all apps in a directory
scapps down app1 app2           # stop multiple apps

scapps info [options]

Display information about running StratusCore Apps.

Options:

  • -v, --verbose - Show detailed container information including status, networks, volumes, and resource limits. Also shows declared dependencies with a live running/stopped indicator for each.

Examples:

scapps info                      # list running apps (simple)
scapps info -v                   # list running apps with detailed container info
scapps info --verbose            # list running apps with detailed container info

scapps attach <app-name-or-dir> [options]

Attach to the live logs of a running application. Accepts either the app name or a path to the app directory.

Arguments:

  • <app-name-or-dir> - App name (e.g., my-app) or path to the app directory

Options:

  • -n <lines> - Show the last N lines of logs without following. When omitted, logs are streamed live.

Examples:

scapps attach my-app             # stream live logs
scapps attach my-app -n 100      # show last 100 lines and exit
scapps attach /path/to/my-app    # attach using directory path

scapps install <app-dir> [tar-file]

Install pre-built application images for offline deployment. This command is designed for air-gapped environments where Docker build or image pull operations are not available.

Arguments:

  • <app-dir> - Path to the app directory containing the image tar file(s)
  • [tar-file] - Optional: specific tar file to load. If omitted, all .tar files in the directory will be loaded

Note: This command is optional and only needed for offline scenarios. The run command handles image building and pulling automatically when internet access is available.

Use cases:

  • deploy applications in air-gapped environments
  • install from a local registry or image archive
  • set up applications without internet access
  • bulk load multiple container images

Examples:

scapps install my-app                    # load all .tar files in my-app directory
scapps install my-app my-app.tar         # load specific tar file
scapps install /path/to/app frontend.tar # load specific tar from path

scapps configure <app-dir ...> [options]

Build and/or pull all Docker images for one or more applications, then package them into a <app-name>-images.tar file per app. Pass a single app directory, multiple app directories, or a parent directory containing multiple app subdirectories.

For each service in scapps.yaml (processed in declaration order):

  1. Services with a build: context — built together via docker compose build.
  2. Image-only services — pulled from the registry.

This makes configure the primary command for preparing images for air-gapped or offline deployment.

Arguments:

  • <app-dir ...> - One or more app directories (each containing scapps.yaml), or a parent directory containing multiple app subdirectories.

Options:

  • --no-cache - Build without using Docker layer cache.
  • --build-only - Skip pulling entirely; all services must have a build: context.
  • --platform <platform> - Target platform for builds and pulls (e.g., linux/amd64, linux/arm64).

pull_policy support:

The pull_policy field on a service in scapps.yaml is respected:

pull_policy Behavior
(unset) Pull if image reference exists, build as fallback
never Build from Dockerfile; error if no build context
always Always pull from registry
build Always build from Dockerfile; error if no build context

Use cases:

  • pre-build and package images for air-gapped or offline deployment
  • test that all images build/pull successfully without running them
  • produce a distributable tar for transport to another machine

Examples:

scapps configure my-app                        # build/pull and save to my-app-images.tar
scapps configure my-app --no-cache             # build without cache
scapps configure my-app --build-only           # build all services, skip pulling
scapps configure my-app --platform linux/amd64 # cross-platform build/pull
scapps configure apps/managed                  # configure all apps in a directory
scapps configure app1 app2                     # configure multiple specific apps

Testing

Unit tests (no Docker required)

These cover command logic, compose generation, runtime features, and error handling using mocks. They run in CI and require no Docker daemon.

cd scapps
uv run pytest

Docker integration tests (local only)

tests/test_docker_integration.py exercises scapps run and scapps down against real containers built from apps/managed/example-app. Excluded from CI and from the default test run. Requires:

  • A running Docker daemon
  • Two external networks created during StratusCore host setup:
    docker network create ros2-internal
    docker network create reverse-proxy-network
    

Run them with:

uv run pytest -m docker

The first run pulls ros:humble-ros-base-jammy and compiles the workspace (~5–10 min, ~1 GB). Subsequent runs reuse Docker's build cache. Images are left behind; containers are torn down after the test.

Run everything

uv run pytest --override-ini='addopts='

Contributing: Creating a Template

Templates use file-based approach with actual template files stored in directories.

Directory Structure

scapps/templates/
├── generic/              # generic template files
│   ├── `Dockerfile`
│   └── `entrypoint.sh`
├── ros2/                 # ROS2 template files
│   ├── `Dockerfile`.template
│   ├── `entrypoint.sh`.template
│   └── dds_profile.xml
├── base.py              # base template class
├── generic.py           # generic template logic
└── ros2.py              # ROS2 template logic

Step 1: Create Template Directory

mkdir scapps/templates/python

Create template files (e.g., Dockerfile, entrypoint.sh, etc.)

Step 2: Create Template Class

# templates/python.py
from pathlib import Path
from typing import Dict
from .base import AppTemplate

class PythonTemplate(AppTemplate): # you can also inherit from GenericTemplate for runtime feature-only templates
    def get_scapps_config(self) -> Dict:
        return {
            "name": self.app_name,
            "services": {
                f"{self.app_name}-service": {
                    "image": f"{self.app_name}:latest",
                }
            },
        }

    def get_dockerfile(self) -> str:
        template_dir = Path(__file__).parent / "python"
        with open(template_dir / "`Dockerfile`") as f:
            return f.read()

    def get_entrypoint(self) -> str:
        template_dir = Path(__file__).parent / "python"
        with open(template_dir / "`entrypoint.sh`") as f:
            return f.read()

    def get_additional_files(self) -> Dict[Path, str]:
        template_dir = Path(__file__).parent / "python"
        files = {}
        for filename in ["requirements.txt", "main.py"]:
            with open(template_dir / filename) as f:
                files[self.app_dir / filename] = f.read()
        return files

Step 3: Register Template

Add to templates/__init__.py:

from .python import PythonTemplate
__all__ = [..., "PythonTemplate"]

Step 4: Add CLI Flag

Update commands/new.py:

def new(
    app_dir: Path = typer.Argument(...),
    ros2: bool = typer.Option(False, "--ros2"),
    python: bool = typer.Option(False, "--python"),
):

    TEMPLATE_PRIORITY = ["generic", "ros2", "python"] # earlier in list trumps later in list

    if ros2:
        selected_templates["ros2"] = ROS2Template

    if python:
      selected_templates["python"] = PythonTemplate

Template Files

For templates requiring files with variable substitution, use .template extension. For example, in the scapps/templates/ros2/, the Dockerfile.template looks a little like so:

# templates/ros2/`Dockerfile`.template
FROM ros:humble-ros-base-jammy
COPY {workspace_name}/ /workspaces/{workspace_name}/

The corresponding Python code looks like the following:

def get_dockerfile(self) -> str:
    template_dir = Path(__file__).parent / "ros2"
    with open(template_dir / "`Dockerfile`.template") as f:
        return f.read().format(workspace_name=self.workspace_name)

Contributing: Creating a Runtime Feature

Runtime features inject Docker Compose configuration (service settings, environment variables, volumes, networks, etc.) into the generated compose file at the moment scapps run is executed. Each runtime feature is a class that inherits from RuntimeFeatureConfig, located in scapps/runtime_features/.

Directory Structure

scapps/runtime_features/
├── base.py          # base class – RuntimeFeatureConfig
├── ros2.py          # ROS2 feature
├── nvidia_gpu.py    # NVIDIA GPU feature
├── ui.py            # UI / reverse-proxy feature
└── __init__.py      # exports

Step 1: Create the Feature Class

Create a new file, e.g. scapps/runtime_features/my_feature.py:

# runtime_features/my_feature.py
from typing import Any, Dict
from .base import RuntimeFeatureConfig


class MyRuntimeFeature(RuntimeFeatureConfig):

    @property
    def name(self) -> str:
        # this is the key that users write in scapps.yaml, e.g. "my-feature: true"
        return "my-feature"

    def get_service_config(self, **kwargs) -> Dict[str, Any]:
        """Return Docker Compose keys merged into each service that enables this feature."""
        return {
            "environment": [
                "MY_ENV_VAR=value",
            ],
            # add any other service-level compose keys here
            # e.g. volumes, devices, cap_add, ulimits, etc.
        }

    def get_network_config(self, **kwargs) -> Dict[str, Any]:
        """Return top-level Docker Compose network definitions needed by this feature.
        Return an empty dict if no custom networks are required (the default)."""
        return {}

get_service_config returns a dict that is deep-merged into every service that has the feature flag set. get_network_config returns entries that are merged into the top-level networks: section of the compose file.

Both methods receive **kwargs with at minimum app_name, service_name, and user_defined_feature (the value written in scapps.yaml – could be True for simple flags or a dict for configurable features like ui).

Step 2: Register the Feature

Add the new class to runtime_features/__init__.py:

from .my_feature import MyRuntimeFeature

__all__ = [
    "RuntimeFeatureConfig",
    "NvidiaGpuRuntimeFeature",
    "Ros2RuntimeFeature",
    "UiRuntimeFeature",
    "MyRuntimeFeature",   # ← add this
]

Step 3: Register with the Generator

Add the feature to _runtime_features inside DockerComposeGenerator.__init__ in commands/utils.py:

from runtime_features import ..., MyRuntimeFeature

# inside DockerComposeGenerator.__init__:
self._runtime_features = [
    Ros2RuntimeFeature(),
    NvidiaGpuRuntimeFeature(),
    UiRuntimeFeature(),
    MyRuntimeFeature(),   # ← add this
]

Step 4: Use It in scapps.yaml

Users can now enable the feature in their scapps.yaml:

# simple boolean flag
name: my-app
services:
  my-service:
    image: my-app:latest
    my-feature: true

Or, for a configurable feature that reads from a dict:

name: my-app
services:
  my-service:
    image: my-app:latest
    my-feature:
      option-a: foo
      option-b: 42

The dict (or True) is passed to get_service_config and get_network_config as user_defined_feature.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

scapps-26.6.0.tar.gz (45.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

scapps-26.6.0-py3-none-any.whl (73.1 kB view details)

Uploaded Python 3

File details

Details for the file scapps-26.6.0.tar.gz.

File metadata

  • Download URL: scapps-26.6.0.tar.gz
  • Upload date:
  • Size: 45.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for scapps-26.6.0.tar.gz
Algorithm Hash digest
SHA256 e71bcb37ddedfc4ee6ef4811caf30717b0ab66a082e839d4321bae28c8aa55c3
MD5 a9d87c9dbe658aa4d53984c5fa5b1204
BLAKE2b-256 d052b91f24cb78f1679cf6b8a28a70a88f5943bfe0ea8df9098f75c613a7308a

See more details on using hashes here.

File details

Details for the file scapps-26.6.0-py3-none-any.whl.

File metadata

  • Download URL: scapps-26.6.0-py3-none-any.whl
  • Upload date:
  • Size: 73.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for scapps-26.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 19b02ae14a033e1ba0ca0d0e02edcaa90f4526c37da3fd4d12e47ebe298857be
MD5 c9584da20c5def2ce4bc27d2019267ec
BLAKE2b-256 ba33902a90fea84ce50c2d7947d6dc171165c5a10db944a3ddef41f2aa04f3d2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page