Summary
The Dockerfile's permissions label includes:
"Binds": ["/usr/blueos/extensions/$IMAGE_NAME:/app"]
This entry breaks the extension when installed through Kraken, for two independent reasons:
-
$IMAGE_NAME is never substituted. All the labels are single-quoted (LABEL permissions='...'), and Docker does not perform build-arg/env expansion inside single-quoted LABEL values (Dockerfile reference, "Environment replacement"). The published image (esalexander/blueos-quickstart:main) carries the literal string $IMAGE_NAME in the label — as well as literal $AUTHOR, $AUTHOR_EMAIL, $MAINTAINER, $OWNER, and $REPO in the authors, company, readme, and links labels, which are all single-quoted too. This is easy to verify: build with --build-arg IMAGE_NAME=foo and docker inspect still shows the literal $IMAGE_NAME.
-
Even with correct substitution, binding over /app shadows the application code. On first install the host directory doesn't exist, so Docker creates it empty and mounts it over /app. The image's code at /app (copied by COPY app /app) becomes invisible to the container. The pip install /app step installs a quickstart distribution but no importable application modules, so litestar run depends entirely on discovering the app from the /app directory contents — which the bind has just emptied.
Observed behavior
Installing the extension via Kraken (POST /kraken/v1.0/extension/install) on BlueOS master starts the container, Docker creates the empty host dir /usr/blueos/extensions/$IMAGE_NAME (literal $IMAGE_NAME as the directory name), mounts it over /app, and the entrypoint crash-loops:
Could not find a Litestar app or factory.
Reproduction (no BlueOS needed)
# Works — /app contains the code baked into the image:
docker run --rm -p 8000:8000 esalexander/blueos-quickstart:main
# Crash-loops — same empty-dir bind that Kraken applies from the permissions label:
mkdir -p /tmp/empty
docker run --rm -p 8000:8000 -v /tmp/empty:/app esalexander/blueos-quickstart:main
Confirmed 2026-07-10 on BlueOS core master (Docker Desktop / arm64, and the failure mode is platform-independent).
Impact
Every extension forked from this template inherits the broken bind, and the failure (crash-loop with an app-discovery error) doesn't obviously point back at the permissions label.
Suggested fix
The only thing in /app that benefits from persistence is the log directory (main.py writes to /app/logs). So either:
- Drop the
Binds entry entirely (logs become ephemeral, extension works), or
- Bind only the logs subdirectory with a literal path, e.g.
"Binds": ["/usr/blueos/extensions/quickstart/logs:/app/logs"], so persistence survives without shadowing the code.
Independent of the choice above, it would make the template more robust to add WORKDIR /app (or an explicit --app main:app) so app discovery doesn't depend on the container's default working directory, and to fix the other single-quoted labels so $AUTHOR/$OWNER/$REPO actually substitute (double-quote the label values, or move the JSON to --label build flags).
Summary
The Dockerfile's
permissionslabel includes:This entry breaks the extension when installed through Kraken, for two independent reasons:
$IMAGE_NAMEis never substituted. All the labels are single-quoted (LABEL permissions='...'), and Docker does not perform build-arg/env expansion inside single-quotedLABELvalues (Dockerfile reference, "Environment replacement"). The published image (esalexander/blueos-quickstart:main) carries the literal string$IMAGE_NAMEin the label — as well as literal$AUTHOR,$AUTHOR_EMAIL,$MAINTAINER,$OWNER, and$REPOin theauthors,company,readme, andlinkslabels, which are all single-quoted too. This is easy to verify: build with--build-arg IMAGE_NAME=fooanddocker inspectstill shows the literal$IMAGE_NAME.Even with correct substitution, binding over
/appshadows the application code. On first install the host directory doesn't exist, so Docker creates it empty and mounts it over/app. The image's code at/app(copied byCOPY app /app) becomes invisible to the container. Thepip install /appstep installs aquickstartdistribution but no importable application modules, solitestar rundepends entirely on discovering the app from the/appdirectory contents — which the bind has just emptied.Observed behavior
Installing the extension via Kraken (
POST /kraken/v1.0/extension/install) on BlueOSmasterstarts the container, Docker creates the empty host dir/usr/blueos/extensions/$IMAGE_NAME(literal$IMAGE_NAMEas the directory name), mounts it over/app, and the entrypoint crash-loops:Reproduction (no BlueOS needed)
Confirmed 2026-07-10 on BlueOS core
master(Docker Desktop / arm64, and the failure mode is platform-independent).Impact
Every extension forked from this template inherits the broken bind, and the failure (crash-loop with an app-discovery error) doesn't obviously point back at the
permissionslabel.Suggested fix
The only thing in
/appthat benefits from persistence is the log directory (main.pywrites to/app/logs). So either:Bindsentry entirely (logs become ephemeral, extension works), or"Binds": ["/usr/blueos/extensions/quickstart/logs:/app/logs"], so persistence survives without shadowing the code.Independent of the choice above, it would make the template more robust to add
WORKDIR /app(or an explicit--app main:app) so app discovery doesn't depend on the container's default working directory, and to fix the other single-quoted labels so$AUTHOR/$OWNER/$REPOactually substitute (double-quote the label values, or move the JSON to--labelbuild flags).