Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions docs/architecture/package-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,43 @@ Packaging containers as `.deb` files provides several advantages over Docker Com
- **Clean removal**: `apt remove` stops containers and cleans up
- **Automatic updates**: Standard `apt upgrade` workflow
- **System integration**: systemd services, icons, dashboard tiles — all in one package
- **Repository distribution**: Published to `apt.hatlabs.fi` like any APT package
- **Repository distribution**: Published to `apt.halos.fi` like any APT package

## What's in a Container App Package?

Each container app package contains:
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/{app-name}/
/var/lib/container-apps/{package}/
├── docker-compose.yml # Docker Compose definition
├── runtime.env # Environment variables
└── icon.png # Application icon
├── 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/
└── {app-name}-container.service # systemd service unit
└── {package}.service # systemd service unit

/usr/share/pixmaps/
└── {app-name}.png # Icon for store and dashboard
└── {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 `docker compose up -d` on start and `docker compose down` on stop.
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](../user-guide/app-data.md#customising-an-app-with-composed).

## App Definition Format

Expand Down Expand Up @@ -75,17 +90,17 @@ See the [Container Metadata Reference](../reference/container-metadata.md) for t

Standard Docker Compose file. The `container-packaging-tools` adds Traefik labels and Homarr labels automatically based on `metadata.yaml`.

### config.yml (optional)
### config.yml

User-configurable settings that are exposed as environment variables in `runtime.env`.
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 ──────────────────────────► grafana-container_12.3.2-1_all.deb
├── metadata.yaml ──────────────────────────► marine-grafana-container_12.3.2-1_all.deb
├── docker-compose.yml
├── config.yml
└── icon.png
Expand Down Expand Up @@ -136,7 +151,7 @@ New categories appear automatically when a package uses a new `category::*` tag

## APT Repository

Container app packages are published to `apt.hatlabs.fi`:
Container app packages are published to `apt.halos.fi`:

| Channel | Purpose | Tag format |
|---------|---------|-----------|
Expand All @@ -153,7 +168,7 @@ The CI/CD pipeline builds packages on push to main, publishes to unstable, and p
apt install marine-grafana-container
→ dpkg extracts files (seconds — package is small)
→ postinst creates data directories, generates secrets
→ systemd starts grafana-container.service
→ 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)
Expand All @@ -166,7 +181,7 @@ apt install marine-grafana-container

```
apt remove marine-grafana-container
→ prerm stops grafana-container.service
→ prerm stops marine-grafana-container.service
→ docker compose down
→ Traefik removes route
→ Homarr adapter detects removal
Expand All @@ -177,10 +192,10 @@ apt remove marine-grafana-container

```
apt upgrade
→ dpkg extracts updated files
→ 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
→ Persistent data in /var/lib/container-apps/{app}/ is preserved
→ compose.d/ overrides and persistent data in /var/lib/container-apps/{package}/data/ are preserved
```

## Version Management
Expand Down
2 changes: 1 addition & 1 deletion docs/developer-guide/adding-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ settings:
description: "Application timezone"
```

These become environment variables in `runtime.env`, editable through the Cockpit configuration UI.
Their defaults land in `/etc/container-apps/<package-name>/env.defaults` and local values in `/etc/container-apps/<package-name>/env`, editable through the Cockpit configuration UI. The service reads both as environment variables.

## Step 4: Add an Icon

Expand Down
4 changes: 2 additions & 2 deletions docs/developer-guide/development-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,15 @@ git commit --no-verify -m "WIP: message"

1. CI builds the Debian package
2. Creates a pre-release tag (`v{version}+{N}_pre`)
3. Publishes `.deb` to `apt.hatlabs.fi` unstable channel
3. Publishes `.deb` to the unstable channel of `apt.halos.fi` (`apt.hatlabs.fi` for repos in the `hatlabs` organisation)
4. Creates a draft GitHub release

### Publishing Stable Releases

1. Test the unstable package on a device
2. Publish the draft release in GitHub UI
3. CI copies the `.deb` to the stable release
4. CI publishes to `apt.hatlabs.fi` stable channel
4. CI publishes to the stable channel of the same repository

## Development Environments

Expand Down
44 changes: 42 additions & 2 deletions docs/user-guide/app-data.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# App Data and Migration

Every container app in HaLOS stores its persistent data on the host filesystem in a predictable location. This page explains where that data lives and how to migrate or back it up.
Every container app in HaLOS stores its persistent data on the host filesystem in a predictable location. This page explains where that data lives, how to migrate or back it up, and how to customise an app's Compose settings.

## Where app data is stored

Expand All @@ -10,7 +10,7 @@ All container apps store data under:
/var/lib/container-apps/<package-name>/data/
```

Each app gets its own isolated directory. Data persists across container restarts and app updates. Removing an app with `apt remove` preserves the data; `apt purge` deletes it.
Each app gets its own isolated directory. Data persists across container restarts and app updates. Removing an app with `apt remove` or `apt purge` preserves the data. `apt purge` deletes only the app's configuration in `/etc/container-apps/<package-name>/`; for a clean reinstall, delete `/var/lib/container-apps/<package-name>/` by hand after purging.

### Finding an app's data path

Expand Down Expand Up @@ -103,3 +103,43 @@ sudo tar xzf backup.tar.gz -C /
# Start the app
sudo systemctl start <package-name>
```

## Customising an app with compose.d

Each container app is defined by a `docker-compose.yml` in `/var/lib/container-apps/<package-name>/`. Do not edit that file: the next package upgrade overwrites it. To change an app's Compose settings, put your own Compose file in the app's `compose.d/` directory:

```
/var/lib/container-apps/<package-name>/compose.d/
```

Files ending in `.yml` or `.yaml` are applied in byte order of filename, after `docker-compose.yml` and, for apps with a web address, after the generated routing labels. A file in `compose.d/` can therefore override any setting of the app. Package upgrades and `apt purge` leave these files in place.

The files merge with the app's definition by Docker Compose's [merge rules](https://docs.docker.com/reference/compose-file/merge/). Lists such as `group_add` add to the existing entries. To clear a setting, tag it with `!reset`; to replace it entirely, tag it with `!override`. HaLOS ships Compose 2.26, which supports both.

### Example: adding a supplementary group

To give the process in one of the app's services an extra group, create for example `compose.d/10-groups.yml`:

```yaml
services:
<service>:
group_add:
- "<gid>"
```

`<service>` is the service name from the app's `docker-compose.yml`. Use the numeric group ID from the host (`getent group <group>` shows it); a group name is looked up inside the container, where it may not exist.

Relative paths in a `compose.d/` file resolve against the app directory, `/var/lib/container-apps/<package-name>/`. A top-level `name:` in a `compose.d/` file has no effect, because the service sets the project name itself.

### Applying the change

Changes take effect when the app restarts. Check the result first, then restart:

```bash
sudo /var/lib/container-apps/<package-name>/compose check
sudo systemctl restart <package-name>
```

`compose check` validates the app's Compose configuration with the same environment the service uses, and lists the active `compose.d/` files. Part of that environment is generated when the service starts, so run the check while the app is running or after it has started at least once since boot. It needs `sudo` because the app's `runtime.env` is readable only by root.

If a `compose.d/` file makes the configuration invalid, the app does not start. See [App fails to start after adding a compose.d file](troubleshooting.md#app-fails-to-start-after-adding-a-composed-file).
4 changes: 2 additions & 2 deletions docs/user-guide/installing-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ The store shows apps from all configured stores. The Marine variant adds a dedic

## Managing installed apps

**Start/Stop/Restart**: Open Cockpit → Services. Find the service (named `<app-name>-container.service`) and use the controls to start, stop, or restart it.
**Start/Stop/Restart**: Open Cockpit → Services. Find the service (named `<package-name>.service`) and use the controls to start, stop, or restart it.

**View logs**: Open Cockpit → Services → select the service → Logs tab. Or use the terminal:

```bash
sudo journalctl -u <app-name>-container.service -f
sudo journalctl -u <package-name>.service -f
```

**Check container status**: Via the Cockpit terminal:
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/system-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ A full command-line terminal in the browser. Useful for running commands without

### Services

View and manage systemd services. You can start, stop, restart, and enable/disable services. Container apps run as systemd services (e.g., `signalk-server-container.service`), so you can manage them here.
View and manage systemd services. You can start, stop, restart, and enable/disable services. Container apps run as systemd services (e.g., `marine-signalk-server-container.service`), so you can manage them here.

### Logs

Expand Down
27 changes: 27 additions & 0 deletions docs/user-guide/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,33 @@ sudo systemctl restart avahi-daemon
sudo systemctl status docker
```

## App fails to start after adding a compose.d file

**Symptom**: After you add or change a file in an app's `compose.d/` directory, the app's service fails to start.

**Cause**: The service validates the app's Compose configuration, including every `compose.d/` file, before it starts the containers. An invalid file fails the start.

**Solution**:

1. Find the error in the service log:
```bash
sudo journalctl -u <package-name> -e
```
A `compose.d fragments: ...` line lists the active files, followed by Compose's error. The error names the service or the YAML line but not the file, so check each listed file.

2. Fix or remove the file, then check the configuration:
```bash
sudo /var/lib/container-apps/<package-name>/compose check
```

3. Clear the failed state and start the service:
```bash
sudo systemctl reset-failed <package-name>
sudo systemctl start <package-name>
```

See [Customising an app with compose.d](app-data.md#customising-an-app-with-composed) for how these files are applied.

## Getting help

- **[GitHub Discussions](https://github.com/halos-org/halos-distro/discussions)** — Ask questions and share ideas with the community
Expand Down
Loading