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.xmlvolume
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
uiruntime feature takes a configuration dict rather than a boolean.portis required and specifies the service's port in the container.pathis 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:
- It searches only within the same parent folder (sibling apps) for the dependency.
- If found in the same folder, it automatically starts the dependency in detached mode first.
- If the dependency is not in the same folder, it will not be auto-started.
scapps runsearches the broader directory tree, shows the location with a manual start hint, and exits with an error. - 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:
Dockerfileandentrypoint.shscapps.yamlconfiguration 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, ROS2Dockerfile, 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 declareddependencieswith 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.tarfiles 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):
- Services with a
build:context — built together viadocker compose build. - 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 containingscapps.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 abuild: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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e71bcb37ddedfc4ee6ef4811caf30717b0ab66a082e839d4321bae28c8aa55c3
|
|
| MD5 |
a9d87c9dbe658aa4d53984c5fa5b1204
|
|
| BLAKE2b-256 |
d052b91f24cb78f1679cf6b8a28a70a88f5943bfe0ea8df9098f75c613a7308a
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19b02ae14a033e1ba0ca0d0e02edcaa90f4526c37da3fd4d12e47ebe298857be
|
|
| MD5 |
c9584da20c5def2ce4bc27d2019267ec
|
|
| BLAKE2b-256 |
ba33902a90fea84ce50c2d7947d6dc171165c5a10db944a3ddef41f2aa04f3d2
|