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:/ sidecarcontainer_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
| Variable | Required | Purpose |
|---|---|---|
AUTH_HASH | Yes | Injected token; pair with index: /?hash=$AUTH_HASH |
BACKEND_HOST | Yes | Internal DNS name of the protected container |
BACKEND_PORT | Yes | Port the backend listens on |
LISTEN_PORT | Yes | Port AppShield listens on |
OIDC_REGISTRAR_URL | Yes | http://auth-registrar:9092 — enables OIDC |
REDIRECT_HOST_SUFFIXES | Yes | Valid OIDC redirect hosts |
CREDENTIAL_VALIDATE_URL | Yes | Validates session against the PCS bridge |
USER / PASSWORD | Optional | Extra basic-auth gate (e.g., $APP_DEFAULT_PASSWORD) |
ALLOWED_PATHS | Optional | Paths 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)
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_shares | Use case |
|---|---|
80 | Sidecars, web frontends |
70 | Main app with background tasks |
50 | Standard backend services |
30 | Databases, caches |
20 | ML / 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:
| Variable | Example | Usage |
|---|---|---|
$APP_DOMAIN | yourname.nsl.sh | https://myapp-${APP_DOMAIN} |
$APP_PUBLIC_IP_DASH | 203-0-113-42 | nip.io / sslip.io labels |
$AUTH_HASH | (generated) | AppShield token |
$APP_DEFAULT_PASSWORD | (generated) | First-boot admin password |
$APP_EMAIL | admin@yourname.nsl.sh | Admin email |
$AppID | myapp | Volume paths: /DATA/AppData/$AppID/ |
$PUID / $PGID | 1000 / 1000 | File ownership |
$TZ | Asia/Seoul | Timezone |