Skip to main content

Adding an App

This is the complete reference for adding a new app to the Yundera App Store. For a quick overview, see Contributing Overview.

Minimal template (SSO via AppShield)

The recommended pattern: an AppShield sidecar fronts your backend and provides Yundera SSO. The backend is never exposed directly — only the sidecar gets routing labels.

name: myapp # lowercase alnum + hyphen, not starting with a digit

services:
myapp: # ← AppShield sidecar (public-facing)
image: ghcr.io/yundera/appshield:2.0.3 # pin the version; never :latest
container_name: myapp # MUST equal top-level name:
restart: unless-stopped
user: "root"
expose:
- 80
labels:
caddy_0: myapp-${APP_DOMAIN}
caddy_0.import: gateway_tls
caddy_0.reverse_proxy: "{{upstreams 80}}"
caddy_1: myapp-${APP_PUBLIC_IP_DASH}.nip.io
caddy_1.import: gateway_tls
caddy_1.reverse_proxy: "{{upstreams 80}}"
caddy_2: myapp-${APP_PUBLIC_IP_DASH}.sslip.io
caddy_2.reverse_proxy: "{{upstreams 80}}"
environment:
AUTH_HASH: $AUTH_HASH
BACKEND_HOST: "myapp-backend"
BACKEND_PORT: "80"
LISTEN_PORT: "80"
OIDC_REGISTRAR_URL: "http://auth-registrar:9092"
REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io"
CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate"
depends_on:
- myapp-backend
cpu_shares: 80
networks:
- pcs

myapp-backend: # ← actual app (internal only)
image: someimage:1.2.3
container_name: myapp-backend
restart: unless-stopped
user: "0:0"
expose:
- 80
environment:
TZ: $TZ
volumes:
- /DATA/AppData/$AppID/data/:/app/data/
cpu_shares: 50
networks:
- pcs

networks:
pcs:
name: pcs
external: true

x-casaos:
architectures:
- amd64
- arm64
main: myapp
index: /?hash=$AUTH_HASH
webui_port: 80
author: Yundera Team
category: Utilities
developer: OriginalDevName
icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/MyApp/icon.png
thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/MyApp/thumbnail.png
screenshot_link:
- https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/MyApp/screenshot-1.png
title:
en_us: My App
tagline:
en_us: One-line description
description:
en_us: |
Full description here.

Naming convention (critical)

The sidecar's container_name must equal the top-level name:. This is load-bearing: the auth registrar derives the OIDC client_id from the container name via PTR lookup on the pcs network. If they don't match, SSO registration silently breaks.

name: myapp
services:
myapp:
container_name: myapp # ← MUST equal name:

Rules:

  • name: / sidecar container_name: lowercase alphanumeric + hyphens, not starting with a digit.
  • Caddy labels go only on the sidecar — never on the backend.
  • The backend has no ports: and no Caddy labels.
  • Never use auth-${APP_DOMAIN} in any Caddy label — it collides with the PCS's Authelia.

AppShield environment reference

VariableRequiredPurpose
AUTH_HASHYesInjected token; pair with index: /?hash=$AUTH_HASH
BACKEND_HOSTYesInternal DNS name of the protected container
BACKEND_PORTYesPort the backend listens on
LISTEN_PORTYesPort AppShield listens on
OIDC_REGISTRAR_URLYeshttp://auth-registrar:9092 — enables OIDC
REDIRECT_HOST_SUFFIXESYesValid OIDC redirect hosts
CREDENTIAL_VALIDATE_URLYesValidates session against the PCS bridge
USER / PASSWORDOptionalExtra basic-auth gate (e.g., $APP_DEFAULT_PASSWORD)
ALLOWED_PATHSOptionalPaths reachable with just the hash token

Volume paths

volumes:
- /DATA/AppData/$AppID/config/:/app/config # app config
- /DATA/AppData/$AppID/data/:/app/data # app data / DB
- /DATA/Media/Music/:/music:ro # shared media (read-only)
Important

Always add a trailing / to the host side of volume paths. Without it, CasaOS cannot resolve $AppID correctly and creates files directly in /DATA/AppData/ instead of inside the app's folder.

# Correct
- /DATA/AppData/$AppID/config/:/etc/myapp

# Wrong — files end up in /DATA/AppData/ directly
- /DATA/AppData/$AppID/config:/etc/myapp

User-facing directories (/DATA/Documents/, /DATA/Downloads/, /DATA/Media/, /DATA/Gallery/) require user: $PUID:$PGID. AppData-only containers can use user: 0:0.

Resource limits

cpu_shares is required on every service. For heavy backends, also set hard limits:

cpu_sharesUse case
80Sidecars, web frontends
70Main app with background tasks
50Standard backend services
30Databases, caches
20ML / batch processing
cpu_shares: 50
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'

Pre-install commands

Run on the host before any container starts:

x-casaos:
pre-install-cmd: |
mkdir -p /DATA/AppData/$AppID/config/ &&
[ -f /DATA/AppData/$AppID/config/settings.ini ] || \
printf '[server]\nport=80\n' > /DATA/AppData/$AppID/config/settings.ini

Rules: idempotent, non-interactive, pinned image tags, no hardcoded passwords (use $APP_DEFAULT_PASSWORD).

System variables

These are injected at install time:

VariableExampleUsage
$APP_DOMAINyourname.nsl.shhttps://myapp-${APP_DOMAIN}
$APP_PUBLIC_IP_DASH203-0-113-42nip.io / sslip.io labels
$AUTH_HASH(generated)AppShield token
$APP_DEFAULT_PASSWORD(generated)First-boot admin password
$APP_EMAILadmin@yourname.nsl.shAdmin email
$AppIDmyappVolume paths: /DATA/AppData/$AppID/
$PUID / $PGID1000 / 1000File ownership
$TZAsia/SeoulTimezone