Package System¶
HaLOS packages container applications as standard Debian .deb packages. This means Docker apps are installed, updated, and removed with APT -- the same package manager used for all other system software.
Why Debian Packages?¶
Packaging containers as .deb files provides several advantages over Docker Compose files alone:
- Single install command:
sudo apt install marine-grafana-container - Dependency management: APT resolves dependencies automatically (e.g., Docker)
- Clean removal:
apt removestops containers and cleans up - Automatic updates: Standard
apt upgradeworkflow - System integration: systemd services, icons, dashboard tiles — all in one package
- Repository distribution: Published to
apt.halos.filike any APT package
What's in a Container App Package?¶
A container app package installs the files under /var/lib, /etc/systemd and /usr/share. Its maintainer scripts and service create the rest:
/var/lib/container-apps/{package}/
├── docker-compose.yml # Docker Compose definition
├── compose # Wrapper that runs docker compose with the overrides
├── compose.d/ # Local Compose overrides (empty when installed)
├── prestart.sh # Runs before each start; writes the runtime environment
├── metadata.yaml # App metadata
├── env.user-template # Template for the local env file
├── config.yml # User-configurable settings
├── env.template # Default environment values
└── data/ # Persistent app data, kept on remove and purge
/etc/container-apps/{package}/
├── env.defaults # Created by postinst from env.template on each install and upgrade
└── env # Created by postinst once; local overrides, kept across upgrades
/run/container-apps/{package}/
└── runtime.env # Environment generated by prestart.sh at each start
/etc/systemd/system/
└── {package}.service # systemd service unit
/usr/share/pixmaps/
└── {package}.{svg,png} # Icon for store and dashboard (extension of the source icon)
The package is intentionally lightweight — it does not contain the Docker image itself. The actual container image is pulled from the registry when the service starts for the first time. This keeps packages small (a few kilobytes) and ensures you always get the exact image version specified in the compose file.
The systemd service runs Compose through the compose wrapper, which passes docker-compose.yml, then the generated routing-labels file for routed apps, then any files in compose.d/. Before starting, the service runs prestart.sh and compose config --quiet, so an invalid project fails the start. It then runs compose up in the foreground, and on stop runs docker compose -p <project> down --remove-orphans. The stop uses only the project name, so it does not depend on the files in compose.d/.
compose.d/ holds local overrides written by the administrator. See App Data and Migration — Customising an app with compose.d.
App Definition Format¶
Developers define container apps using three files:
metadata.yaml¶
The central definition file. Contains package metadata, version, tags, routing configuration, and Docker labels.
name: Grafana
app_id: grafana
version: 12.3.2-1
upstream_version: 12.3.2
description: Data visualization and monitoring platform
homepage: https://grafana.com/
maintainer: Hat Labs <support@hatlabs.fi>
license: AGPL-3.0
tags:
- role::container-app
- field::marine
- category::visualization
- category::monitoring
- interface::web
routing:
auth:
mode: forward_auth
web_ui:
enabled: true
port: 3000
protocol: http
See the Container Metadata Reference for the full schema.
docker-compose.yml¶
Standard Docker Compose file. The container-packaging-tools adds Traefik labels and Homarr labels automatically based on metadata.yaml.
config.yml¶
User-configurable settings that are exposed as environment variables through /etc/container-apps/{package}/env.defaults, and overridable in /etc/container-apps/{package}/env.
container-packaging-tools¶
The generate-container-packages command converts app definitions into Debian packages:
apps/grafana/ generate-container-packages
├── metadata.yaml ──────────────────────────► marine-grafana-container_12.3.2-1_all.deb
├── docker-compose.yml
├── config.yml
└── icon.png
The tool:
- Validates the metadata against the schema
- Generates Traefik routing labels from the
routingconfiguration - Generates Homarr dashboard labels from the metadata
- Creates Debian packaging files (control, rules, postinst, prerm, postrm)
- Builds the
.debpackage withdpkg-buildpackage
Tag-Based Categorization¶
Packages use a tag-based categorization system with debtag facets. This enables domain-specific stores to organize packages meaningfully.
Key Facets¶
| Facet | Purpose | Examples |
|---|---|---|
field:: |
Application domain | field::marine, field::core |
category:: |
User-facing store category | category::navigation, category::monitoring |
role:: |
Package type | role::container-app |
interface:: |
UI type | interface::web |
How Store Filtering Works¶
- A store configuration defines filter rules (e.g.,
include_tags: [field::marine]) - The backend filters all packages matching those tags
- Categories are auto-discovered from
category::*tags on matched packages - The store UI displays packages organized by category
# store/marine.yaml
id: marine
name: Marine Navigation & Monitoring
filters:
include_tags:
- field::marine
category_metadata:
- id: navigation
label: Navigation & Charts
icon: MapIcon
New categories appear automatically when a package uses a new category::* tag -- no store configuration update needed.
APT Repository¶
Container app packages are published to apt.halos.fi:
| Channel | Purpose | Tag format |
|---|---|---|
trixie-stable |
Production releases | v{version}+{N} |
trixie-unstable |
Pre-release testing | v{version}+{N}_pre |
The CI/CD pipeline builds packages on push to main, publishes to unstable, and promotes to stable when a GitHub release is published.
Install Lifecycle¶
Installation¶
apt install marine-grafana-container
→ dpkg extracts files (seconds — package is small)
→ postinst creates data directories, generates secrets
→ systemd starts marine-grafana-container.service
→ prestart.sh computes runtime configuration
→ docker compose up
→ Docker pulls image layers from registry (may take minutes on first install)
→ Container starts
→ Traefik detects labels, creates route
→ Homarr adapter adds dashboard tile
Removal¶
apt remove marine-grafana-container
→ prerm stops marine-grafana-container.service
→ docker compose down
→ Traefik removes route
→ Homarr adapter detects removal
→ dpkg removes package files
Updates¶
apt upgrade
→ dpkg extracts updated files, overwriting docker-compose.yml
→ systemd restarts the service
→ docker compose pulls new image (only if upstream version changed) and recreates container
→ compose.d/ overrides and persistent data in /var/lib/container-apps/{package}/data/ are preserved
Version Management¶
Container app packages use a two-part version scheme:
- Upstream version: The application version (e.g.,
12.3.2) - Revision: Auto-incremented by CI (e.g.,
-1,-2)
The full Debian version is {upstream}-{revision} (e.g., 12.3.2-1). Revisions increment automatically on each CI build, even if the upstream version hasn't changed.