Adding Apps¶
This guide covers creating a new container application for HaLOS.
The Quick Way: Ask Claude¶
If you're working with Claude Code from the halos-distro workspace (see Workspace Setup), adding an app is a conversation:
"Add a new marine container app called yacht-radar. It uses the image
example/yacht-radar:2.1.0, has a web UI on port 8080, and should use forward auth. Put it in the navigation category."
Claude reads the existing apps in halos-marine-containers/apps/, follows the established patterns, and produces the complete set of files: metadata.yaml, docker-compose.yml, icon, and configuration. It handles the conventions documented below -- auth modes, Traefik labels, tag taxonomy, volume paths -- without you needing to look them up.
This works because the workspace has rich context: every existing app serves as an example, and each repository's AGENTS.md documents the conventions. The rest of this page documents those conventions for reference.
App Structure¶
Each app lives in a directory under apps/ in either halos-core-containers (pre-installed) or halos-marine-containers (store apps):
apps/my-app/
├── metadata.yaml # Package metadata, tags, routing
├── docker-compose.yml # Docker service definition
├── config.yml # User-configurable settings (optional)
├── prestart.sh # Pre-start script (optional)
└── icon.png # Application icon (256x256, PNG)
Step 1: Create metadata.yaml¶
The metadata file defines everything about the package:
name: My App
app_id: my-app
version: 1.0.0-1
upstream_version: 1.0.0
description: Short description of the application
long_description: |
Longer description with details about features
and capabilities. Shown in the app store.
homepage: https://example.com/
maintainer: Hat Labs <support@hatlabs.fi>
license: MIT
tags:
# Domain (required for store filtering)
- role::container-app
- field::marine
# User-facing categories
- category::monitoring
# Technical characteristics
- interface::web
debian_section: web
architecture: all
depends:
- docker.io (>= 20.10) | docker-ce (>= 20.10)
routing:
subdomain: my-app
auth:
mode: forward_auth # or: oidc, none
web_ui:
enabled: true
port: 8080
protocol: http
Authentication Modes¶
Choose the appropriate auth mode in routing.auth.mode:
| Mode | When to use |
|---|---|
forward_auth |
Default. App has no SSO support. Traefik handles auth transparently. |
oidc |
App has native OIDC support (e.g., Grafana, Homarr). |
none |
App should be publicly accessible or handles its own auth. |
Tags¶
Tags determine where the app appears in the store:
field::marine-- Include in the Marine storecategory::navigation-- Appear under "Navigation & Charts" categoryrole::container-app-- Identifies this as a container appinterface::web-- Has a web UI
See the Container Metadata Reference for all available fields.
Step 2: Create docker-compose.yml¶
Write a standard Docker Compose file. Do not include Traefik, Homarr, or mDNS labels -- these are generated automatically from metadata.yaml.
services:
my-app:
image: example/my-app:${UPSTREAM_VERSION:-1.0.0}
container_name: my-app
restart: unless-stopped
env_file:
- runtime.env
volumes:
- ${CONTAINER_DATA_ROOT}/my-app/data:/app/data
networks:
- halos-proxy-network
logging:
driver: journald
options:
tag: "{{.Name}}"
networks:
halos-proxy-network:
external: true
Key conventions:
- Restart policy: Always
unless-stopped - Logging: Use
journalddriver so logs are accessible via Cockpit - Network: Join
halos-proxy-networkfor Traefik routing - No port exposure: Do not add a
ports:section unless the app needs non-HTTP protocol access - Data volumes: Use
${CONTAINER_DATA_ROOT}for persistent data
Host Networking Apps¶
If the app needs hardware access (USB, serial, CAN bus):
services:
my-app:
image: example/my-app:latest
network_mode: host
# No networks section when using host networking
Add host_port to routing in metadata.yaml:
Step 3: Add config.yml (Optional)¶
Define user-configurable settings:
settings:
MY_APP_PORT:
default: "8080"
description: "HTTP port for the application"
MY_APP_TIMEZONE:
default: "${TZ:-UTC}"
description: "Application timezone"
These become environment variables in runtime.env, editable through the Cockpit configuration UI.
Step 4: Add an Icon¶
Include a icon.png file (256x256 pixels, PNG format). This is displayed in:
- The container app store
- The Homarr dashboard tile
- The Cockpit service list
Step 5: Build and Test¶
Build the Package¶
This requires container-packaging-tools to be installed.
Test Locally¶
- Copy the
.debto a test device - Install:
sudo apt install ./my-app-container_1.0.0-1_all.deb - Verify the container starts:
docker ps - Check the subdomain resolves:
https://my-app.halos.local - Verify the app appears in the Homarr dashboard
- Test removal:
sudo apt remove my-app-container
Step 6: Submit a PR¶
- Create a feature branch in the appropriate repository
- Add your app directory under
apps/ - Create a PR with a clear description
- CI will build and validate the package
For marine apps, add the app to halos-marine-containers. For core infrastructure apps, add to halos-core-containers.
Example: Complete Marine App¶
Here's a complete example of a marine monitoring app:
name: Marine Monitor
app_id: marine-monitor
version: 1.0.0-1
upstream_version: 1.0.0
description: Real-time marine sensor monitoring
homepage: https://example.com/marine-monitor
maintainer: Hat Labs <support@hatlabs.fi>
license: MIT
tags:
- role::container-app
- field::marine
- category::monitoring
- interface::web
- use::monitoring
debian_section: net
architecture: all
depends:
- docker.io (>= 20.10) | docker-ce (>= 20.10)
routing:
subdomain: marine-monitor
auth:
mode: forward_auth
web_ui:
enabled: true
port: 3000
protocol: http
services:
marine-monitor:
image: example/marine-monitor:${UPSTREAM_VERSION:-1.0.0}
container_name: marine-monitor
restart: unless-stopped
env_file:
- runtime.env
volumes:
- ${CONTAINER_DATA_ROOT}/marine-monitor/data:/data
networks:
- halos-proxy-network
logging:
driver: journald
options:
tag: "{{.Name}}"
networks:
halos-proxy-network:
external: true