Skip to content
SHC Docs

ManifestAdapter interface + the coolify/umbrel/casaos adapters (2026-07-17)

Status: DESIGN. Part of the platform-compat program — faithful capture of docs/handoff/2026-07-16-hel1-reprovision-and-platform-compat.md (§C, §C2, §C2b, §C5, §E), no new design. Siblings: 2026-07-17-origin-ingest-gate.md (the pipeline adapters run inside), 2026-07-17-app-metadata-schema.md (the canonical metadata adapters remap into), 2026-07-17-notes-templating.md (the notes surface adapters target), 2026-07-17-origin-repos-and-migrations.md (catalog registration + the adapted→native migration path).

Goal: run apps packaged for Umbrel / CasaOS / Runtipi / Coolify / CapRover / Portainer / Cloudron on SHC — gated (opt-in per app, <origin>-compat mode; native SHC apps untouched).

Each origin has its own manifest format, secret contract, proxy model, and volume layout. Teaching the SHC enrichment/backup/ingress/SSO core about any of them would couple the core to N evolving foreign formats. The core must stay platform-agnostic; all origin knowledge lives at the edge, in per-origin adapters that run once, at ingest.

Terminology (collision-checked against the codebase 2026-07-16)

Section titled “Terminology (collision-checked against the codebase 2026-07-16)”
  • origin — a foreign app ecosystem: umbrel, casaos, runtipi, coolify, cloudron; native apps have origin shc. (platform is TAKEN — 513 hits, platform:manage/PlatformAdmin/shc_config.platform; origin’s 58 hits are all incidental.)
  • adapter — the per-origin INGEST-TIME transpiler (origin manifest + compose → real SHC package). NOT “shim”: a shim implies a runtime layer, and there is none — the transpile is materialized at ingest.
  • adapted app vs native app — what an adapter produces vs a first-party package; the enrichment core cannot tell them apart.
  • origin repo — a foreign catalog registered via shc repo add --origin <origin> <url>.
  • importshc origin import: lift a LIVE install off a real origin box onto SHC as an adapted app, data included.
  • migration — the data transpile, adapted → native (2026-07-17-origin-repos-and-migrations.md).

Core architecture — a pluggable ManifestAdapter

Section titled “Core architecture — a pluggable ManifestAdapter”

parse(origin manifest + compose) -> normalized SHC package. Each origin is a normalization FRONT-END; the SHC enrichment/backup/ingress/SSO CORE stays platform-agnostic. Adding a brand-new origin touches only the edge, never the core — an adapter owns four responsibilities:

// shape only — enumerate / parse / gate / transpile
type ManifestAdapter interface {
Enumerate(catalog) []appRef // origin's package layout
Parse(appRef) originManifest // manifest + compose
Gate(originManifest) []reject // the ingest reject taxonomy
Transpile(originManifest) shcPackage // materialized SHC package
}

plus, per origin: the env+secret contract map, the importer’s box-layout knowledge, conformance fixtures (sample origin apps → golden normalized packages), and the licensing check for that catalog.

Umbrel adapter (self-contained single-UI HTTP subset only)

Section titled “Umbrel adapter (self-contained single-UI HTTP subset only)”

Pre-pass: strip app_proxy; synthesize host_spec from the app_proxy service’s APP_HOST/APP_PORT env, NOT the manifest port (the manifest port is where the proxy listens; using it routes to nothing for much of the catalog); rewrite host bind-mounts (${APP_DATA_DIR}) → named volumes; inject the APP_* env contract; skip integration injection. Then the unchanged core runs traefik/network/volume/placement/health.

Secrets → vault (opinionated): Umbrel derives secrets from a per-device APP_SEED on disk; SHC instead mints APP_SEED + APP_PASSWORD and stores them in the vault (deployment-scoped, backed-up, rotatable in general), injecting them as env so the app’s own derivation still works but SHC owns them. Exception: APP_SEED is mint-once, IMMUTABLE, non-rotatable — apps persist seed-derived creds (admin hash in their DB, initdb passwords) at first-init; rotation = lockout the platform can’t see; restore must replay the SAME seed. No standard vault-rotation UX on it.

Volumes: Umbrel packages ship seed files copied into ${APP_DATA_DIR} pre-start; named volumes populate from the IMAGE, not the package → volume seed-file injection is a missing core primitive (init-job / volume-seeding step). Overlapping subpath mounts need a stated shared-parent-volume rule; UID/ownership first-boot failures are expected; compat volumes must pin to LOCAL (never network-backed) providers.

Scope: self-contained single-UI HTTP apps only. dependencies / exports.sh carriers, torOnly, host-network/device apps are rejected at the ingest gate.

Compose service templates with magic env: SERVICE_FQDN_*host_spec; SERVICE_PASSWORD_*/_BASE64_* → vault mint via the @secret contract. Bundled templates run as-is. Coolify’s SERVICE_* magic env is the best-aligned secret/ingress contract to import, and the cheapest adapter to build.

Umbrel-path machinery (self-contained compose, bundled DB); metadata read from the x-casaos: compose extension.

Umbrel/CasaOS apps are self-contained — each bundles its own DB in its own compose → run as-is, no SHC plug by default. Optional bridges: front with the forward-auth gate for SSO (transparent) — DEFAULT-ON for every adapted app, per-app opt-out requiring ingest-gate evidence of self-auth (many Umbrel apps have no auth of their own; publishing them ungated on public DNS + LE = unauthenticated admin panels with a green padlock). Cross-app dependencies (mostly Bitcoin/Lightning app-to-app) are rejected in v1. Cloudron is the outlier — not compose (CloudronManifest.json + Dockerfile + declared addons postgresql/mysql/redis/mail/oidc/ldap) — its addons map ~1:1 to SHC plugs/magic tokens (POSTGRES/KEYCLOAK/MAIL), so its adapter is different (Dockerfile→service, addons→plugs) but the cleanest integration fit.

Original draft (optimized for catalog size): Umbrel → CasaOS → Coolify → CapRover/Portainer/Cloudron. The evaluation reversed it: SHC users look like Coolify/Cloudron users, not Umbrel users, and Coolify is also the cheapest adapter.

Revised: Coolify → Umbrel (self-contained single-UI HTTP subset only) → CasaOS → stop. CapRover and Portainer are dropped from the roadmap.

Cloudron is gated on three unscoped prerequisites — it is the best architecture fit (addons≈plugs, built-in SSO+backups) AND the worst supply-chain/legal fit:

  1. Registry licensing / image-build pipeline — Cloudron images come from a paid registry, so SHC would need its own image-build pipeline.
  2. LDAP addon — no SHC analog (the oidc addon maps to the keycloak plug; ldap only to the forward-auth gate, which is not the same contract).
  3. recvmail — no SHC analog.

Licensing generally is a legal decision, not engineering: the Umbrel platform is PolyForm Noncommercial; catalog + gallery screenshots need diligence before any verbatim ingest, given SHC is commercially deployed.

Capability matrix — packaging + integration model (Table A)

Section titled “Capability matrix — packaging + integration model (Table A)”

The load-bearing axis: does the compose bundle EVERYTHING, or does the app integrate with platform-managed external services?

PlatformPackage formatCompose bundles everything?External / managed servicesSHC compat handling
Umbrelumbrel-app.yml + docker-compose.ymlYES — every app ships its own DB/cache in its own composenone (its dependencies = other Store APPS via exports.sh env, mostly bitcoin/lightning)run the stack as-is, bundled DB included; dependencies/exports.sh REJECTED at ingest (v1)
CasaOScompose + x-casaos: extensionYES — self-containednoneUmbrel-path machinery; metadata read from x-casaos
Runtipiconfig.json + composeYES — self-containednonesame; form_fields → typed vars
Coolifycompose service template + SERVICE_* magic envmostly — templates bundle their DBsoptional standalone managed DBs; magic env mints creds/FQDNsbundled runs as-is; SERVICE_FQDN_*host_spec, SERVICE_PASSWORD_*/_BASE64_* → vault mint
CapRovercaptain-definition / one-click YAMLpartial — single-service apps; DBs installed as SEPARATE one-click apps, env-wiredquasi (separate app instances, manual wiring)dropped from roadmap
PortainerJSON app templateYES per templatewhatever env points at (unmanaged)dropped from roadmap
CloudronCloudronManifest.json + Dockerfile — no composeNO — single app container, read-only rootfsYES — platform addons: postgresql/mysql/mongodb/redis/mail/oidc/ldap/localstorage/scheduler via CLOUDRON_* envaddons map ~1:1 to SHC plugs/magic tokens (POSTGRES/KEYCLOAK/MAIL); GATED on the three prerequisites above
SHC nativeapp.yaml + compose + plugseither — bundle-your-own OR integrateplugs / magic tokens, cross-tenant

Capability matrix — feature × platform, with the compat disposition (Table B)

Section titled “Capability matrix — feature × platform, with the compat disposition (Table B)”

Terse cells; = the platform has nothing for this.

CapabilityUmbrelCasaOSRuntipiCoolifyCapRoverPortainerCloudronSHC compat handling
Web exposureapp_proxy + port/pathx-casaos port/schemeport + exposableSERVICE_FQDN_*captain subdomainpublished portshttpPort (+multiDomain)host_spec → traefik + LE + DNS self-reg (Umbrel: from app_proxy APP_HOST/APP_PORT env, NOT manifest port)
Non-HTTP ports (L4)common (P2P: 8333/9735)somesomesometemplate portsrareexplicit L4 publish policy or ingest REJECT (unique-exposer + tenant port allocation)
Auth / SSOapp_proxy session auth (many apps have NO own auth)oidc/ldap addonsforward-auth gate DEFAULT-ON, per-app opt-out w/ evidence; Cloudron oidc → keycloak plug; ldap = NO analog (gate)
SecretsAPP_SEED derivationplaintext envgenerated field valuesSERVICE_PASSWORD_*/_BASE64_*$$cap_gen_random_hexplaintext envplatform-injected addon credsmint-once → vault; APP_SEED IMMUTABLE non-rotatable; generator grammars → @secret mint
Volumesbind ${APP_DATA_DIR} (+ package seed files)bind /DATAbind app-datanamed/bindnamedtemplate volumeslocalstorage /app/datarewrite → named volumes + provider; seed-file init job; subpath shared-parent rule; LOCAL providers only
Backupswhole-device (Backups/Rewind) — not per-applimited archivescheduled DB dumps (S3)app snapshotper-app encrypted scheduledper-app quiesce+capture auto-backup.yaml (physical; same-DB-major restore contract; no exporter.yaml → second-class export/import)
Jobs / init / croncompose entrypoints + exports.shcomposecomposescheduled taskspre-deploy scriptscheduler addon + start.sh*-init.job.yaml + lifecycle hooks + crons; exports.sh NOT executed (reject or curated reimpl)
Healthcheckssometimes composecomposehealthCheckPathsynthesize compose healthcheck where declared; else healthy-because-running caveat
Cross-app depsdependencies + exports.sh + cross-app DATA mountsattachable shared DBsseparate one-click DB appsaddons (managed)v1: REJECT dep-carrying apps at ingest (backup-boundary violation class); Cloudron addons → plugs
User config at install– (env fixed)x-casaos env labelstyped form_fieldsenv promptsvariables promptsenv label/selectenv via UImap to the typed vars DSL (@string/@secret/@boolean/@port…)
Post-install messagedefaultUsername/defaultPasswordtips (verify name in census)docs linkinstructions.endpostInstallMessagenotes template (sensitive-safe render of minted creds + resolved host)
Store metadatarich (tagline,gallery,developer,…)x-casaos fieldsconfig.json fieldstemplate metadataone-click fieldstitle/logo/categoriesrich (mediaLinks, changelog, …)canonical SHC-vocab fields (census-driven) + compat: passthrough bag
Updates / migrationsstore updates + hooksimage pullversion bumpredeployimage updatemanualplatform-managed w/ migrationsre-transpile new version + redeploy; DB-image pin is part of the backup/restore contract
Resource limitssomememoryLimitquota DEFAULTS for compat apps (noisy-neighbor guard); map memoryLimit
Remote access / TorTor hidden svc + Tailscalepublic DNS + LE instead; torOnly REJECT; outbound Tor = optional sidecar
Host devices / host netsome (Home Assistant, USB)commonsomeraretemplate-dependent– (contained)ingest REJECT (network_mode: host, /dev passthrough, privileged)
Multi-instance / scalescalingsome (multiDomain)swarm replicas / x-shc-scale where the app tolerates it; else single-replica pin

Takeaway: SHC is the superset on the server/platform axis (per-app backups, integrations, multi-tenant/multi-cloud). Cloudron is the closest architectural analog (addons≈plugs, built-in SSO+backups) and arguably the highest-value non-compose adapter — gated as above. Coolify’s SERVICE_* magic env is the best-aligned secret/ingress contract to import.

Backups: what adapted apps get, stated up front

Section titled “Backups: what adapted apps get, stated up front”

Quiesce-and-capture makes physical capture of a paused bundled DB crash-consistent and restorable — BUT only into the same DB major version (image pins become part of the backup contract), and adapted apps are second-class for export/import (no exporter.yaml). State it, don’t let users discover it. The path out of second-class is a migration to native apps (2026-07-17-origin-repos-and-migrations.md).

The steady-state cost = tracking N evolving manifest formats. Required, or the adapters rot in a quarter:

  • pinned upstream snapshots (every sync records the origin sha);
  • re-transpile CI diffing the normalized output on each re-sync (stage 7 of the ingest pipeline);
  • conformance fixtures per origin — sample origin apps → golden normalized packages, living with the adapter;
  • resource-quota defaults for quota-less manifests;
  • synthesized healthchecks (Cloudron healthCheckPath → compose healthcheck; Umbrel apps will read healthy-because-running);
  • image pinning / provenance policy for unreviewed third-party compose.
  • Adapters are ingest-time transpilers, not runtime shims; the core cannot tell adapted from native.
  • Priority: Coolify → Umbrel subset → CasaOS → stop; CapRover and Portainer dropped.
  • Cloudron gated on registry-licensing/image-build-pipeline, LDAP-plug, and recvmail — all three unscoped.
  • Forward-auth gate default-on for adapted apps; opt-out per app with ingest-gate evidence.
  • APP_SEED immutable; other minted secrets follow the normal vault contract.
  • Compat volumes on LOCAL providers only.
  • Scoping the three Cloudron gates (each is its own investigation).
  • Volume seed-file injection as a core primitive: init-job shape and the shared-parent-volume rule for overlapping subpath mounts.
  • Umbrel’s implements (drop-in provider contract) is conceptually adjacent to SHC’s integration providers — future look, not v1.
  • Licensing diligence per catalog (legal, not engineering) before any verbatim ingest is served beyond private use.