Adapt, generate, migrate, and validate 1Panel App Store (AppStore/appstore) packages from Docker Compose, AppSpec, and aaPanel/Baota sources. Use when users ask for a 1Panel app skill, 1Panel application adaptation, appstore packaging or submission preparation, v1-to-v2 migration, Docker Compose conversion, localized metadata, strict-store validation, or batch app adaptation on Linux and GitHub.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Adapt, generate, migrate, and validate 1Panel App Store (AppStore/appstore) packages from Docker Compose, AppSpec, and aaPanel/Baota sources. Use when users ask for a 1Panel app skill, 1Panel application adaptation, appstore packaging or submission preparation, v1-to-v2 migration, Docker Compose conversion, localized metadata, strict-store validation, or batch app adaptation on Linux and GitHub.
1Panel AppStore Adapter
Adapt Docker applications into reviewable 1Panel AppStore packages, then validate their structure, metadata, Compose configuration, localization, and upgrade behavior against source-backed rules.
Start Here: Choose One Route
Work from the skill directory so every scripts/... and references/... path resolves. Read this SKILL.md through its final line. First classify the input, then follow exactly one route below. Open and read every reference named by that route through its final line before running its command, and state how the references affected the plan or artifact. Every route ends at Completion Gates. If official Docker deployment evidence is unavailable, stop and report the missing evidence; do not guess a package. Publishing is outside this skill.
New app from official Docker/Compose: Read references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md, then record the preflight decision before scaffolding. Stop for platform_stack_terminal; stop for specialized_conditional until every named prerequisite is proven. --out-dir is a parent: for example, run bash scripts/scaffold-v2.sh --app-key <app-key> --title <title> --image <image> --version <version> --out-dir "$RUN_ROOT/artifact" --source-repository <url> --source-docker-docs <url> --source-compose-file <url>, then require "$RUN_ROOT/artifact/<app-key>" to directly contain data.yml, source-evidence.json, and <version>/; reject <app-key>/<app-key>. Review every generated file against the authoritative Compose and replace all placeholders. For callback, origin, or other public URL fields, never synthesize localhost or 127.0.0.1: expose a required public URL as a required form field; leave an optional public URL empty or omit it and document the affected features.
AppSpec input: Read references/appspec.md, references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md. Run python3 scripts/generate-from-appspec.py --spec <appspec.json> --out-dir <out-dir> --validate --require-validate. Review the generated topology, variables, metadata, translations, lifecycle ledger, and validation report against the AppSpec and official sources.
Existing v1 or mixed package: Read references/source-policy.md, references/topology-preflight.md, references/upgrade-maintenance.md, and references/lifecycle-safety.md. Run bash scripts/migrate-v1-to-v2.sh --src <app-dir> --out <out-dir> [--version <source-version>] [--target-version <target-version>] --source-repository <url> --source-docker-docs <url> --source-compose-file <url>. Review the migrated root/version metadata, Compose, .env.sample, lifecycle scripts, and upgrade compatibility; source URL flags may be omitted only when the source package already has valid source-evidence.json.
aaPanel/Baota input: Read references/baota-migration-workflow.md, references/baota-app-format.md, references/baota-to-1panel-mapping.md, references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md. Precheck the complete prepared input with python3 scripts/import-baota-app.py --input <baota-app-dir> --precheck-only --report <report.json>; for a batch add --batch. Then convert one selected version per invocation with python3 scripts/import-baota-app.py --input <baota-app-dir> --out-dir <out-dir> --version <exact-version> --validate --require-validate. Review every output as converted_candidate against official upstream evidence; never infer version order from Baota metadata.
Update an existing v2 app: Read references/upgrade-maintenance.md, references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md. Compare the old and new package before editing; use only the needed helper commands below. Review image lineage, persisted data, changed variables, dependencies, lifecycle scripts, and direct-upgrade behavior, then run final validation.
Validate only: Read references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md; also read references/upgrade-maintenance.md when several versions or an update are involved. Start with bash scripts/validate-v2.sh --dir <app-dir> and review every failure and warning before strict validation. Validation does not authorize guessing or silently patching unknown semantics.
PHP runtime: Read references/php-runtime.md, references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md before choosing a generator. Follow the runtime-specific package shape, review picker metadata and actual runtime integration, then use the applicable helper commands and final validation below. Do not treat a PHP runtime as an ordinary website/tool app.
Exact Helper Commands
Use scripts for their named job instead of manually recreating their behavior. Review each diff after a mutating helper; patch helpers normalize structure but cannot prove application semantics.
Confirm authoritative repository, Docker documentation, Compose/image evidence, license, and topology; record unsupported facts instead of inventing them, and stop when the selected preflight route says to stop.
Preserve the selected upstream service graph, dependencies, internal networks, persistence, and security controls. Give every Compose service labels.createdBy: "Apps" and a unique container_name based on ${CONTAINER_NAME} unless current 1Panel runtime evidence requires another shape.
Build an authoritative control inventory from the selected official launch command, Compose, image, and entrypoint before editing. Record services, image commands and users, environment variables and fixed values, healthchecks, dependencies, ports, networks, mounts and mount options, capabilities, security options, privileged mode, devices, and host namespaces. Compare the final Compose against that inventory, preserve every source-backed control, and justify every omission or change with official source or target-platform evidence. A fixed upstream hardening value is not an optional install-form setting; keep it fixed unless evidence supports changing it.
Build a capability contract for each official deployment shape. Record its user-visible capabilities and the exact service, image, environment, and dependency combination that enables each one. Preserve the full upstream capability set by selecting either the full upstream default or an officially/target-platform-proven capability-equivalent alternative. Select a reduced-capability profile only when the user explicitly requests it; disclosure does not substitute for capability preservation. For an authorized reduced profile, prove the remaining combination is supported, list the exact unavailable features in the README and report, and use any required compatible image variant; do not call it the default or source-equivalent topology. Missing compatibility evidence blocks scaffolding.
Before assigning any route, write a deployment-shape census. Start from official install guides and their indexes or README, then enumerate every referenced sample or Compose root in the application and companion deployment repositories. For each discovered root, trace its include/template chain and record whether it is selected or rejected and why. A route conclusion that considers only the most obvious sample is incomplete.
After the census, evaluate each deployment shape independently in a deployment-shape decision ledger. For each shape, record its exact root and release, support scope, default capability set, external dependency ownership, persistence boundary, migration and upgrade boundary, and disposition as selected, conditional, or rejected with evidence. Treat a documented operator-managed dependency as an external-service boundary, not a missing service. A more complex production topology does not invalidate a smaller official topology, and a production-ready label is scoped evidence, not an exclusivity claim. Invalidate a smaller shape only with exact-version evidence in one of these categories: officially required, exclusive, deprecated, or capability-incomplete. Assign platform_stack_terminal only after every capability-complete official shape has been evaluated and none can enter or with satisfiable named prerequisites.
Rule Priority
Judge every rule in this order:
Runtime and source-code hard rules from 1Panel-dev/1Panel
Official 1Panel wiki and official docs
Official appstore repository conventions
External references and practical articles
Only rules backed by runtime behavior or explicit official documentation should block generation or validation. Repository conventions are guidance unless validator mode explicitly upgrades them.
Source Policy (Evidence First)
Before adapting a new candidate, read references/source-policy.md, references/topology-preflight.md, and references/lifecycle-safety.md. Complete the topology decision before scaffolding or deployment testing: ordinary candidates may proceed, specialized conditional candidates need their recorded prerequisites satisfied, and platform-stack terminal candidates stop unless the user opens a separately scoped project.
Collect evidence according to source priority:
User-provided official repository/documentation > official image docs > official appstore structure facts > third-party examples
Without official Docker evidence, stop expanding; do not guess images, ports, volumes, UID/GID, or dependency injection methods
When using third-party images, user must explicitly accept, and record source and risk in delivery notes
Preserve high-risk runtime permissions when official compose, documentation, or source evidence shows that a core application feature requires them. This includes Docker or Podman Socket mounts, privileged: true, cap_add, host networking, host PID/IPC namespaces, device mappings, and relaxed security_opt settings. Do not remove a required permission merely to reduce scanner findings.
Keep required high-risk access no broader than upstream needs, test the feature that depends on it, and document the exact permission, feature dependency, and host impact in the app README and delivery notes. A risk label is required; deletion is not the default remediation.
For existing app updates, version additions, image lineage changes, dependency changes, volume/env rewrites, or lifecycle script edits, also read references/upgrade-maintenance.md before changing files. Treat upgrade safety as part of the adaptation contract, not a post-submit note.
For PHP runtime work, especially when converting a historical package such as php-unofficial into a real 1Panel PHP runtime, also read references/php-runtime.md. PHP runtimes are not packaged like ordinary website/tool apps, and the runtime picker behavior is source-backed rather than guessable from generic appstore patterns.
Recommended Directory Structure (Store-Aligned)
Field Hierarchy Constraints (Aligned with Official apps/ Facts)
Note: In the official repository (1Panel-dev/appstore dev branch), the data.yml field hierarchy under v2 structure is very stable; this skill's validate-v2.sh --strict-store performs strict validation according to this.
Language codes: Official write as zh-Hant (note uppercase H); old write zh-hant is only for compatibility, recommend unified replacement.
Application-level: apps/<app>/data.yml
Top-level only allows: name / tags / title / description / additionalProperties
Top-level type appearing: WARN only (historical/non-standard field; official regular write doesn't place at top level)
architectures: Must be in additionalProperties.architectures (100% present in official samples)
Can be null or object (but under strict-store, recommend object with required fields filled)
formFields must be in additionalProperties.formFields (top-level formFields not allowed)
Prohibit architectures: Version-level data.yml (same level as docker-compose.yml) should not have architectures (neither top-level nor inside additionalProperties)
additionalProperties.description uses i18n map, must complete 8 languages: en/zh/zh-Hant/ja/ko/ru/ms/pt-br.
root data.yml uses hierarchical structure: top-level tags and additionalProperties.tags both exist and are semantically consistent (allow redundant expression).
Content consistency (strong constraint): root data.ymltitle:, the following top-level description:, and additionalProperties.shortDescZh: must be the same short text (try to be one sentence).
Translation constraint (strong constraint): additionalProperties.description must be the multilingual translation of the above shortDescZh (not repetition of project name/title).
architectures (only root data.yml) represents Docker image supported architecture list; should be in additionalProperties.architectures, using hierarchical array (e.g., - amd64 / - arm64); if cannot reliably obtain (e.g., no manifest info/offline), default only fill - amd64.
formFields[].type allowed set (based on official dev/apps real samples)
number
password
select
text
apps
service
Notes:
Official repo has both patterns:
type: apps + child.type: service
Direct type: service
Therefore validator should allow service in formFields[].type, but adapted artifacts can prefer apps + child.service to express "dependency selection → service instance selection" two-step semantics.
rule common set (for this skill validation)
paramPort (most common, for PANEL_APP_PORT_*)
paramExtUrl
paramCommon
paramComplexity
Treat this as an allowed set, not a requirement to populate rule. Normally only externally published port fields use rule: paramPort; omit rule from secrets, URLs, selectors, domains, and ordinary text unless a demonstrated 1Panel parser or application-format requirement needs it.
formFields[].edit (consumer-backed editability)
Based on official v2 app library docker-compose.yml and same-directory version-level data.yml actual write:
edit is bool in DTO (default equivalent to false), but official library explicitly writes edit: true for many fields.
Choose edit from the value lifecycle, not from a blanket editable default:
Use edit: true only when the steady-state consumer reads the changed value directly, or an idempotent reconciliation or migration safely applies it to persisted state.
Use edit: false for a value generated once, identity-bearing configuration, panel injection, or any value whose persisted consumer is intentionally not regenerated. Typical injection keys include PANEL_DB_*, PANEL_REDIS_*, and PANEL_MINIO_*.
Dependency selection fields (type: apps/service) may use edit: true only when the linked-resource lifecycle supports changing the selection; enumeration alone is not migration evidence.
Validation strategy: validate-v2.sh --strict-store will WARN when a required non-selector field lacks an explicit edit decision (not FAIL, to avoid rejecting official historical output). Resolve the warning from the consumer lifecycle; do not silence it mechanically with edit: true.
Before preserving an upstream-bundled database/cache sidecar, inspect the target appstore and a current 1Panel store for reusable runtime applications and service registration. When a compatible 1Panel-managed runtime exists and the application supports an external service, prefer the panel runtime selector path and panel fixed envKeys; do not wait for a later review reminder to replace the bundled dependency.
Dependency preference order:
A compatible, selectable 1Panel-managed runtime exposed through /apps/services/<key> and the corresponding resource records.
A documented external service configuration when no reusable panel runtime is available.
The upstream-bundled database/cache sidecar only when the runtime selector path is unavailable, unregistered, incompatible, or the upstream application requires the bundled topology.
For applications supporting multiple database engines, keep one app key and expose the engines that have independent panel/runtime evidence through one type: apps plus child.type: service selector. Map engine-specific ports through selector params. Do not create database-specific app keys or version directories merely to represent engine choice, and do not advertise an engine that only passes static compose validation.
If users are expected to choose a reusable 1Panel-managed dependency from the install UI, do not leave the dependency host field as plain type: text.
For database-family selectors, prefer type: apps plus child.type: service.
For single-step selectors such as Redis service reuse, prefer type: service with the dependency key (for example key: redis) on the host envKey field.
A package that only accepts manual host input is not equivalent to a package whose UI can actually select a store/local dependency app.
Scaffold supports optional injection template:
When running scripts/scaffold-v2.sh, add --with-panel-deps (or alias --with-panel-db-redis), will automatically add above DB/Redis related formFields in generated <version>/data.yml (including labelEn/labelZh + label map, includes zh-Hant).
Key points for adaptation:
Treat store-runtime discovery as an adaptation preflight step: search existing app definitions for the dependency key, query the panel store metadata, and verify that an installed instance appears in /apps/services/<key> before deciding the final service topology.
Treat the selector value as a 1Panel app key, not a display-label alias. mysql and localmysql are distinct values and must be verified independently through /apps/services/mysql and /apps/services/localmysql; do not advertise one because the other works.
version/data.yml uses type: apps + child.type: service to inject PANEL_DB_HOST (reference 1Panel store app common dependency injection pattern).
Keep service enumeration separate from database lifecycle integration. A running option returned by /apps/services/<key> proves the selector can enumerate that instance, but not that 1Panel created or linked an application database. When the form also requests PANEL_DB_NAME, PANEL_DB_USER, and PANEL_DB_USER_PASSWORD, require install and upgrade evidence that the host envKey is present in the services payload and the installed app reports linkDB: true or the expected resourceKeys; then verify schema/user creation and cleanup behavior separately.
For packages that require a runtime administrator password to initialize several upstream-defined schemas, use the actual password of the selected installed runtime. A form default is only a template and must not be treated as the current runtime credential. Do not change a shared runtime's authentication mode to make a smoke test pass.
App uninstall does not universally own external database data. Confirm whether selector-created linked resources are removed by 1Panel, and explicitly clean only task-owned schemas/users during tests. Preserve manually managed external databases unless the user separately authorizes their removal.
When converting an existing package from manual host input to a selector-backed dependency, keep the effective runtime envKey stable when possible. For example, changing REDIS_HOST from type: text to type: service is usually upgrade-safe because existing .env values still map to the same compose/app variable.
If the selector conversion requires a renamed envKey or adds a new selector-driving field such as PANEL_DB_TYPE, treat that as an upgrade migration item and backfill it in when possible.
version data.ymlformFields[].label should keep both labelZh/labelEn and label map (compatible with different repositories/versions).
volumes host paths default prefer falling under ./data/* subdirectories (e.g., ./cache normalizes to ./data/cache).
Named volumes (named volume) maintain upstream semantics:
If upstream compose uses volumes: <name>: and service mounts - <name>:/path, adaptation should try to preserve.
root data.yml restriction: When application uses named volume as main data volume, root data.ymladditionalProperties.limit set to 1.
Recommended write (compatible with 1Panel scenario):
volumes:
zeroclaw-data:
name: zeroclaw-data
And keep in service:
volumes:
- zeroclaw-data:/zeroclaw-data
When upstream explicitly uses a host path, preserve its bind semantics and mount options. Keep an official operator-edited path fixed and package-local; add an APP_DATA_DIR_* form only when users actually need a selectable host path.
Uninstall script: Anchor Compose to the version directory instead of relying on the caller's working directory. Preserve bind-mounted data and persistent named volumes by default. Use --volumes only after the mount ledger proves every affected named volume is package-owned, disposable, and approved for deletion. Match 1Panel's current Compose command selection: prefer Compose v2, then fall back to the supported legacy binary. Minimal safe default:
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$ROOT_DIR"
if docker compose version >/dev/null 2>&1; then
docker compose down
elif docker-compose version >/dev/null 2>&1; then
docker-compose down
else
echo "Docker Compose is not available" >&2
exit 1
fi
Multi-Service Compose Hint Mechanism (Hint Only, No Auto-Modification)
When scaffold/migrate output docker-compose.yml detects multiple services containing postgres/mysql/mariadb/redis keywords, it outputs hints:
Suggest considering --with-panel-deps to switch to 1Panel store dependency injection (PANEL_DB_* / PANEL_REDIS_*) mode
Remind to map application's own DATABASE_* / REDIS_* variables to panel fixed variable names in compose
Can manually run hint check on any compose: bash scripts/hint-panel-deps.sh <docker-compose.yml>
docker-compose.yml ↔ data.yml Field Constraints (Strong Constraints)
ports → formFields (Official Common Pattern)
Based on official dev/apps version-level samples:
External port field mostly uses envKey: PANEL_APP_PORT_HTTP (also exists PANEL_APP_PORT_HTTPS/SSH/API/...).
Corresponding formFields rule basically fixed as:
type: number
required: true
rule: paramPort (official almost always uses it)
default is specific port number (1..65535)
docker-compose.yml usually writes:
- "${PANEL_APP_PORT_HTTP}:<container_port>"
Adaptation suggestions:
Port envKey must have prefix: Unified use PANEL_APP_PORT_ prefix (e.g., PANEL_APP_PORT_HTTP / PANEL_APP_PORT_API).
Single-port web application: Prioritize expose PANEL_APP_PORT_HTTP.
Multi-port application: Use PANEL_APP_PORT_HTTP/HTTPS/API/SSH/... semantic naming (keep all uppercase, underscores), all use rule: paramPort.
Broad host port ranges are fragile in 1Panel/Docker maintenance flows. If upstream exposes hundreds of protocol ports, do not blindly make the whole range the appstore default; prefer the minimal common port(s), document how to add protocol-specific ports, and only keep a range when install/restart smoke proves it is stable.
volumes → Data Directory Fields (Official vs Adaptation Artifact Convention)
Official library compose mostly uses bind mount (e.g., ./data:/data, ./conf/xx:/etc/xx), but official version-level data.ymlusually doesn't parameterize paths through APP_DATA_DIR_* formFields (prefers directly writing fixed relative paths).
Adaptation artifact convention:
Preserve every authoritative Compose mount mechanism, source/target, read/write mode, propagation setting, and security option such as z/Z as a required delivery default. Change one only when target-platform documentation or runtime evidence proves it incompatible and a reviewed replacement preserves the same contract.
Keep documented operator-edited configuration binds fixed and package-local, such as ./config:/etc/app:Z. Do not replace them with named volumes or add path forms by default.
Add APP_DATA_DIR (or a semantic numbered variant) only when users need to select or migrate the host path. Use the same variable in Compose, data.yml, .env.sample, and lifecycle scripts.
Do not convert an authoritative bind to a named volume merely because direct host access appears unnecessary or the lifecycle seems equivalent. Apply the same target-platform incompatibility and equivalent-replacement gate before changing the mount mechanism.
type: apps + child.type: service (nested injection)
Direct type: service (also exists in official library)
Adaptation suggestions:
External service/dependency injection prioritize child pattern: Prioritize type: apps + child.type: service (express "dependency type selection → service instance selection" two-step semantics).
Only when panel interaction clearly only needs "select a service instance" and doesn't need dependency type selection, use type: service.
Enum/boolean must use select:
If compose has FOO: "true"/"false", log level, run mode etc. "finite set" variables, should define in <version>/data.ymlformFields with type: select (e.g., values: true/false or debug/info/warn/error), avoid free text.
Example: ZEROCLAW_ALLOW_PUBLIC_BIND switch should be select (true/false).
values[].label must always be pure string, prohibit multi-language map (strong constraint):
1Panel backend will deserialize formFields[].values[].label to string; if incorrectly written as multi-language object (e.g., label: { en: ..., zh: ... }), will directly error: cannot unmarshal object into Go struct field ... values.label of type string.
This constraint applies to all type: select / type: apps / any enum items with values:, not just booleans.
❌ Wrong:
values:
- label:
en: 'True'
zh: '是'
value: "true"
✅ Correct:
values:
- label: "true"
value: "true"
Boolean text values label must add quotes: When values[].label is true/false, must write as string ("true"/"false"), avoid YAML parsing as boolean causing frontend/panel issues.
❌ Wrong: label: true
✅ Correct: label: "true"
compose variable closure (strong constraint):
docker-compose.yml variables ${VAR} / ${VAR:-default} / ${VAR?msg} etc., by default must find corresponding declaration in version .
Registry-backed batch adaptation notes
Use these notes when adapting many apps from one image publisher or registry namespace. They are conventions for keeping generated artifacts reviewable; do not treat publisher-specific behavior as a universal runtime rule unless the upstream source confirms it.
When a persistence path is exposed through data.yml (for example APP_DATA_DIR, APP_CONFIG_DIR, or numbered variants), lifecycle scripts must use the same variable names with safe defaults instead of hardcoding ./data. A common pattern is DATA_DIR="${APP_DATA_DIR:-./data}" before creating or cleaning host paths.
Do not assume 1Panel injects every path form field into the lifecycle-script process environment. If scripts/init.sh or scripts/upgrade.sh needs a persistence-path variable, derive the app root from the script location, read <app-root>/.env as a fallback, strip matching single or double quotes from values written by 1Panel, and resolve relative paths against the app root before creating or changing permissions. The generated lifecycle scripts implement this behavior; do not regress to current-working-directory-relative ./data handling.
.env.sample is a standalone-compose convenience file, not the source of truth for 1Panel runtime parameters. 1Panel installs render their own .env from form values and platform injection. Do not reverse-fill .env.sample from install-time state, and do not make packaged apps depend on env_file: ./.env.sample for 1Panel-only runtime behavior.
If a package needs an additional runtime defaults file such as dify.env, treat it as a packaged pre-./.env layer: document that role clearly, keep user-specific secret samples out of it where possible, and rely on generated ./.env values or explicit compose env mappings to supply real install-time secrets.
For secret-like form fields, do not combine random: true with a fixed weak sample default unless the target panel is known to replace it before submission. If the install should generate a value, prefer an empty default and let the panel/test runner supply the random secret.
If docker-compose.yml references ${CONTAINER_NAME}, its .env.sample value must be non-empty and deterministic: CONTAINER_NAME=<app-key>-compose-check. Keep it out of formFields; 1Panel still replaces it at install time. Validate the exact sample with docker compose --env-file .env.sample config and do not inject a different validation-only value.
App display names should identify the application, not the image publisher, unless the publisher is part of the product name. Put image provenance in README, source notes, or delivery notes instead of root / fields.
Runtime startup lessons
Use these checks when an app needs a wrapper command before delegating back to the official image entrypoint or command.
If the wrapper starts as root to repair bind-mount permissions and then drops privileges with setpriv, gosu, su-exec, or similar, also set HOME, USER, and LOGNAME for the target application user before exec. Some runtimes and package managers keep using /root after UID/GID changes unless the environment is corrected; for example, pnpm can fail with EACCES while opening /root/.config/pnpm/config.yaml.
For apps using the official PostgreSQL 18+ images, do not blindly mount persistent data to /var/lib/postgresql/data. PostgreSQL 18 images use major-version-specific cluster directories and the official image error message recommends mounting /var/lib/postgresql so future pg_upgrade --link flows do not cross mount boundaries. If you customize PGDATA or keep the older /var/lib/postgresql/data path, require direct compose and 1Panel smoke evidence before delivery.
When generated config needs 1Panel random password fields, prefer generating the config inside the application container at startup, where compose environment variables are definitely present. Do not assume scripts/init.sh receives every form-generated secret.
Avoid one-shot init sidecars for required startup work when targeting 1Panel app installs. 1Panel may rewrite restart policies during deployment, so service_completed_successfully and short-lived init containers can become fragile. Prefer idempotent initialization in the main service startup path, a long-running helper, or a dependency healthcheck that can be retried safely.
Treat database initialization assets as runtime-critical. In containerized 1Panel/Docker setups, a relative single-file bind such as ./schema.sql:/docker-entrypoint-initdb.d/schema.sql can resolve against a path the Docker daemon cannot see and appear inside the container as a directory. Prefer an existing store-proven data-directory staging pattern, an image-contained asset, or another initialization path that does not depend on an unverified daemon-visible source path. Test the actual mounted file type and database tables on a clean data directory.
A healthy container and HTTP 200 do not prove a database-backed app is usable. Before delivery, verify an app-specific business-ready path on clean state: required tables/schema exist and a documented login works, or the official first-run setup page/API is reachable and requires no preparation unavailable to a panel user.
i18n Translation Quality Check Switch
To avoid "format compliant but translation lazy", validate-v2.sh adds configurable translation quality check:
--source-evidence-mode warn|required|off
warn: warn but continue when source-evidence.json is missing or invalid (default)
required: require source-evidence.json; with --strict-store, also require application-license evidence plus hash-bound asset and redistribution-material delivery evidence
description: only validate root additionalProperties.description
labels: only validate version formFields[].label
all: both (default)
--i18n-allow-english-labels <CSV>
Short label English whitelist (e.g., API,URL,ID,OAuth,JWT), avoid mis-killing technical words.
labels scope supplement: If formFields[] only has labelEn/labelZh and missing label: multi-language map, validate-v2.sh will now give WARN, and clarify version formFields.label expected to complete 8 languages: en/zh/zh-Hant/ja/ko/ru/ms/pt-br.
Default strategy:
description more strict (prevent whole sentence English pseudo-translation)
formFields.label hierarchical processing (short words allow whitelist)
Placeholder translation policy: scaffold-v2.sh generates 8-language description using the app title as placeholder. This is intentional — the scaffold provides a valid structure, and users should replace placeholders with real translations before submission. The i18n check flags these as warnings (not errors) to remind users to complete translations.
Default fallback tag is Tool (no longer use Docker).
Default logo:
This repository's project-authored source is assets/default-logo.svg; its terms are in assets/default-logo.LICENSE.txt (MIT, copyright 2026 okxlin).
The deterministically rendered assets/default-logo.png is 180x180 and has SHA-256 a8f604f27c3451536301f1a4ca7ac5ae8c479312a225c42c4dc0edda2a20bf76.
scaffold-v2.sh and generate-from-appspec.py copy the PNG, the source SVG at <app>/assets/default-logo.svg, its required license text, and hash-bound redistribution evidence only when they actually select this fallback. They never label a pre-existing or imported logo as the default asset.
Logo normalization suggestion: Before delivery, prioritize unifying <app>/logo.png to 180x180 PNG; processing should maintain original logo ratio, don't stretch, don't compress, don't deform. If original exceeds 180x180, only do proportional shrink; if original is smaller, don't force enlarge. Finally . If want to balance repository size and store loading efficiency, recommend compressing to . Can directly use: .
Output shape
The scaffold command produces a directory in this shape:
Core principle: .env.sample must list all environment variables used in docker-compose.yml, including those not declared in data.yml formFields.
1Panel boundary: .env.sample is for users who want to run the packaged compose outside 1Panel. It is not the runtime contract for a 1Panel install, and form submission should not mutate .env.sample. If a packaged app needs 1Panel runtime values, express them through data.yml form fields, panel-injected variables, compose defaults, or explicit lifecycle logic instead of relying on .env.sample as an installed env_file.
Recommended values rule: .env.sample should contain working default values where possible, so users can docker compose up directly after copying:
Variables with official defaults → use the default (e.g., TZ=Asia/Shanghai, AUTH_ENABLED=false)
Required variables without defaults → provide example values with placeholder markers (e.g., CPA_BASE_URL=http://host.docker.internal:8317, CPA_MANAGEMENT_KEY=replace-with-your-management-key)
Optional variables without defaults → leave empty (e.g., REDIS_QUEUE_ADDR=)
Variable categories:
Category
In data.yml formFields?
In .env.sample?
Example
User-configurable ports
Yes
Yes
PANEL_APP_PORT_HTTP=8080
User-configurable settings
Yes
Yes
APP_DATA_DIR=./data
Panel-injected (DB/Redis)
No (auto-injected by 1Panel)
Yes
PANEL_DB_HOST=
Container name
No (auto-generated by 1Panel)
Yes
CONTAINER_NAME=<app-key>-compose-check
Why CONTAINER_NAME in .env.sample but not in formFields:
1Panel automatically generates CONTAINER_NAME at install time (based on app key + instance ID)
Users should not manually configure it, so it's excluded from formFields
However, .env.sample must include a non-empty deterministic value because Compose references ${CONTAINER_NAME} and an empty container_name is invalid.
Run docker compose --env-file .env.sample config against the exact delivered sample. Do not repair the sample only in the validation command.
Validation: validate-v2.sh should check that all ${VAR} references in docker-compose.yml have corresponding entries in .env.sample.
Regression test: scripts/test-env-sample-closure.sh provides a standalone closure check. Usage:
The default capability set excludes optional features that are not enabled by default. Do not pre-publish optional listeners, mounts, devices, capabilities, or host permissions. Keep one only when the selected profile explicitly enables and fully configures it and the capability is validated; "for later configuration" is not evidence that it belongs in the selected profile.
Apply the historical official source fallback before stopping for missing deployment evidence. A single 404 or moved page is not a terminal condition: inspect the exact release tree, version history, official documentation repositories, pinned historical Compose, and official image/startup sources, then record every attempted official source. When deployment evidence comes from a companion repository, pin its exact commit and trace the selected root through its actual include/template chain; an alternative file on a moving default branch is not evidence that the selected topology uses it. Stop only after those paths are exhausted and a concrete unsafe unknown remains; name the missing contract fact and the unsafe guessed decision it blocks.
A terminal route does not waive OCI inspection. Inspect the OCI manifest and OCI config for each published image used by every viable official shape, including base, bootstrap, or runtime images named by launchers. If no immutable published registry artifact exists or registry access remains unavailable after the approved checks, record the unavailable registry fact and limit negative security or runtime claims; do not infer image controls from YAML alone.
Build a startup configuration contract by inspecting every available exact-version authority among the official install examples, configuration reference, image defaults, entrypoint, and startup source; record unavailable authorities instead of requiring all of them to exist. Do not treat the selected official Compose as an exhaustive startup configuration list. Scope the ledger to required, startup-fatal, stability-bearing, or coupled inputs for the selected topology, plus any selected or default-dependent optional inputs. Record each value's default, empty-value rule, consumer, validation rule, secret format, stability requirement, and cross-field constraints, and prove the consumer and startup behavior from source or exact-image/runtime evidence. Reconcile every required value into the final Compose, data.yml, .env.sample, or source-backed lifecycle logic, then trace it to the application consumer. Whenever a form envKey, label, or type changes, re-review its default, rule, and values; keep every non-empty, non-secret, Compose-consumed form default consistent with .env.sample as defined in references/lifecycle-safety.md. Treat validated form values as literal data during template generation, never as regular-expression or replacement-program input. required: true or a generic paramComplexity rule does not prove the application-specific validation contract; enforce the full rule before Compose starts or stop. A successful Compose render does not prove that the panel default passes application startup validation; an unresolved or omitted required value blocks a pass claim.
Record source-evidence.json.images[] for every selected version's Compose service image, including database, cache, browser, migration, and helper services. Bind the version, exact service, resolved image reference, and matching registry digest; prefer a digest-pinned runtime reference, but do not claim that a tag-only reference is immutable. The legacy single imageEvidence covers only a one-image Compose. For a multi-platform image, record the index digest separately from each platform child digest. Associate a platform child digest only from that same registry descriptor; do not infer it from descriptor order, the current host architecture, or a separate query. Recheck every declared platform-to-digest mapping before reporting it.
For every service with a writable bind, add runtimeIdentity to its selected images[] entry (or legacy imageEvidence) with numeric startupUid, startupGid, steadyStateUid, steadyStateGid, an exact-version HTTPS source, and writableBindOwner. Use host-init only with generated --dir-owner/--fixed-dir-owner coverage for every bind, image-managed only with a separate HTTPS ownerEvidence, and root-runtime only when steadyStateUid is 0. Delivery validation rejects missing identity evidence or an unmatched non-root owner plan.
Keep a minimal install form: expose only settings users need for the selected default topology. Do not mirror every optional upstream profile or environment variable; remove disabled profiles or resolve reviewed package defaults while maintaining Compose/form/.env.sample closure.
For callback, origin, and externally visible base URLs, use the exact current upstream variable and expose one full public URL when user input is required. Do not reconstruct it from host, protocol, or port fields unless version-matched official evidence proves that contract equivalent. Keep the external URL separate from the internal listener: for a reverse proxy, preserve the documented internal scheme/port, TLS termination boundary, and trusted-proxy or hop settings. Do not set an internal listener to HTTPS without the application's required certificates and source-backed configuration.
Treat .env as untrusted data in lifecycle scripts. Never source or eval it; parse only exact known keys, strip quotes, validate values, and resolve relative paths from the app root.
Complete the references/lifecycle-safety.md path and mount ledger, including each source-backed mount mechanism, mount options, and operator-access contract. Treat source, target, read/write mode, propagation, and security options in authoritative Compose as required delivery defaults; preserve them exactly unless target-platform evidence proves incompatibility and a reviewed replacement preserves the same contract. A named volume is not a drop-in replacement for a configuration bind used by official host-side editing, backup, restore, or support procedures; preserve that fixed package-local bind and do not add an APP_DATA_DIR form unless a selectable host path is actually required. Derive startup and steady-state runtime UID/GID separately from the published OCI configuration, Compose user, and verified entrypoint/process behavior; keep mutable host paths package-local and confined before creation, permission changes, or cleanup. When the selected authoritative deployment uses a named volume for a non-root writable path, preserve that volume. When it uses a bind, use the explicit --dir-owner/--fixed-dir-owner helper with a source-backed identity on a direct child of the trusted version root, then verify the exact owner, mode, and a write probe as that identity. Change the mechanism only through the same target-platform incompatibility and equivalent-replacement gate. A plain root-created 0755 bind directory blocks delivery. Do not recursively change ownership on an unconfined or symlinked path.
Report an observed owner or mode as an observation tied to the invoking UID/GID and umask. Claim a portable guarantee only when the delivered lifecycle script explicitly enforces that owner or mode and the exact-artifact test verifies it.
Create or validate the exact source file for every file bind before Compose starts. Prove each generated secret format against the application contract, keep stable secrets across upgrade, URL-encode URL credentials, and apply the official escaping rules to every other connection-string grammar.
Replace placeholders with real product metadata and meaningful translations in all required locales. English fields must contain English. Record the application and asset licenses; when exact redistribution terms require attribution, copyright or license text, source disclosure, or NOTICE delivery, include that required material in the package instead of relying on a link. When a license has material use restrictions, name and link it in the README instead of keeping it only in machine-readable evidence. Verify an asset's redistribution basis separately from the application code license; for an unresolved asset license or trademark permission, use the neutral placeholder immediately rather than shipping the asset with a future-confirmation note.
Render and validate the exact delivered artifact without creating then removing a file it needs. Ensure init.sh, upgrade.sh, and uninstall.sh exist and retain executable mode in the delivered tree. Run baseline validation first, then bash scripts/validate-v2.sh --dir <app-dir> [--version <version>] --strict-store --i18n-mode strict --source-evidence-mode required --require-delivery-evidence; unresolved failures block a pass claim.
Before finalizing the README or report, build a configuration claim ledger and compare each statement with the exact Compose, data.yml, .env.sample, and lifecycle scripts. Classify every described value as fixed, defaulted, generated, optional, or user-configurable. If an editable form field controls it, describe it as user-configurable with that default rather than fixed; contradictions block a delivery-ready claim.
For every form field, record its install-time and steady-state consumer before choosing edit. A value generated once, written into persistent configuration, or identity-bearing must use edit: false unless an idempotent reconciliation or migration applies later edits to the real persisted consumer. Do not expose a control that only changes .env while the application continues using an older persisted value.
Test in a real 1Panel development/test instance: clean install, application-specific readiness, restart, upgrade when applicable, uninstall, and task-owned cleanup. Report artifact paths, evidence, checks, risk-bearing permissions, assumptions, warnings, and every unexecuted runtime gate; static validation or HTTP 200 alone is insufficient. Distinguish files in the delivered AppStore package from run-only evidence caches. Do not claim that a run-only cache path is present in the delivered package.
scripts/upgrade.sh
PostgreSQL-only rule: if the app relies on panel-side PostgreSQL provisioning (CreateDatabase in install task logs), runtime validation should use a real 1Panel-installed PostgreSQL app in the same panel. Pointing the service field at an arbitrary external hostname can bypass the intended provisioning path and create misleading failures.
For that PostgreSQL-only path, keep the application PostgreSQL user (PANEL_DB_USER) distinct from the PostgreSQL service admin/root account. Reusing the admin username can make a correct package fail during install with User already exists.
Do not automatically generalize those PostgreSQL-specific behaviors to MySQL; verify MySQL-linked adaptations from their own 1Panel task/runtime evidence before carrying the rule over.
For format-sensitive secrets, do not assume a generic random password is a valid application value. Examples: Laravel APP_KEY expects Laravel-compatible key material, while Mastodon ACTIVE_RECORD_*, SECRET_KEY_BASE, OTP_SECRET, and VAPID_* have upstream-specific generator commands and formats.
When official docs expose a generator helper, prefer scripts/init.sh / scripts/upgrade.sh to generate or normalize those values from the official image/helper command instead of shipping a fixed sample secret in data.yml or trusting a generic panel-generated random string.
If scripts/init.sh or scripts/upgrade.sh replaces a panel-provided secret with a normalized/generated value, persist that final value under the app's configurable data path and restore it during later upgrades. Real 1Panel upgrades can replay the original install form value instead of the mutated .env, which can break apps that silently rotate APP_KEY, DB_PASSWORD, or similar persisted secrets.
Keep that secret-persistence rule distinct from the PostgreSQL-only provisioning notes above: the replay problem can affect MySQL-, PostgreSQL-, or non-DB secret fields, even though the dependency-provisioning behavior is not shared across engines.
If the compose uses network_mode: host, its listener occupies the host port directly even without a ports: block. Keep the install-form envKey as PANEL_APP_PORT_* so 1Panel performs its port-occupation check, map the upstream listener variable from that key (for example, NETDATA_LISTENER_PORT=${PANEL_APP_PORT_HTTP}), and verify the application consumes it. A disabled/fixed port field must still match the real built-in listener instead of being treated as a free-to-randomize published port.
For host-network adaptations tested from a containerized smoke runner, runtime probing may need the Docker host gateway (or another host-reachable address) rather than 127.0.0.1 inside the panel container.
docker-compose.yml if application uses DATABASE_* variables, need to map in compose:
DATABASE_HOST: ${PANEL_DB_HOST}
DATABASE_USER: ${PANEL_DB_USER}
DATABASE_PASSWORD: ${PANEL_DB_USER_PASSWORD}
DATABASE_DBNAME: ${PANEL_DB_NAME}
Redis password similarly: REDIS_PASSWORD: ${PANEL_REDIS_ROOT_PASSWORD} (if application field name differs, map as needed).
Validation must prove the selector path, not only connectivity. A smoke/install report should show the dependency host envKey under the install payload services object; manually injecting only params.REDIS_HOST=... or params.PANEL_DB_HOST=... is not enough evidence that the packaged UI selector works.
data.yml
formFields.envKey
Few variables implicitly provided by 1Panel/runtime, maintained in references/implicit-envkeys.md.
Before adding new implicit variables, confirm they truly belong to platform injection; don't put ordinary application variables into whitelist.
External port envKey must use PANEL_APP_PORT* prefix:
1Panel official convention: envKeycontainingPANEL_APP_PORT prefix will be recognized as port type, used for port occupation check during installation.
Therefore: all "externally exposed host port" fields, envKey must be named PANEL_APP_PORT_* (e.g., PANEL_APP_PORT_HTTP).
If upstream uses HOST_PORT naming, should map in compose:
HOST_PORT=${PANEL_APP_PORT_HTTP} (or directly use ${PANEL_APP_PORT_HTTP} as ports left side).
compose image field unified double quotes (strong constraint):
docker-compose.ymlimage: always use double quotes, especially digest (@sha256:...) or variable form, reduce YAML/panel parsing edge issues:
For required but allow empty default keys (common in API_KEY), recommend ${VAR:-} form consistent with upstream.
Alias variable (synonym key) handling: If upstream provides API_KEY and XXX_API_KEY synonym variable names, adaptation should only expose one main field (usually use upstream default API_KEY), another in compose comment form (don't inject two keys simultaneously, avoid ambiguity).
Avoid map write:
environment:
API_KEY: ${API_KEY}
name
title
If the image registry provides both latest and numbered release tags, keep the moving latest version plus the newest numbered version unless the target appstore policy asks for deeper history.
Formize user-meaningful environment variables in data.yml, but skip high-surface or topology-changing settings unless they are understood and tested: password-hash alternatives, certificate/private-key path overrides, remote SQL ingestion, debug/client-IP logging, external object storage, remote auth backends, container runtime/Podman socket access, privileged mode, and sidecar generation variables owned by another UI. If one of these settings is an upstream-required part of the app's core runtime rather than an optional form control, preserve it directly in Compose and add explicit risk documentation instead of silently dropping it.
For sidecar compositions, preserve the upstream service topology only when the selected application actually needs it. Do not expose sidecar bootstrap variables that conflict with the main application's UI-driven configuration workflow.
When the main service joins both a shared external network such as 1panel-network and an internal app network, avoid generic internal service names in host variables (redis, mongo, mysql, postgres, db). Prefer app-prefixed service names such as <app>-redis or explicit internal network aliases so Docker DNS cannot resolve a same-name service from the shared network.
For GHCR or another token registry, do not treat one authenticated-client denied response as proof that the package is private. Follow references/source-policy.md: verify an anonymous manifest token, then pull with a temporary empty DOCKER_CONFIG without changing the user's registry login state. Local cache success alone is not fresh-install evidence.
center overlay logo onto 180x180 transparent canvas
no more than 10KB
bash scripts/normalize-logo.sh <logo.png>
Compose top-level version handling: Delivered to 1Panel docker-compose.yml should remove top-level version: field (e.g., version: '3.8'), avoid deprecated/ignored warnings in 1Panel / Docker Compose logs. Adaptation should directly start from services: organizing compose content, unless encountering special scenarios requiring old parser.
Service-level createdBy label convention: Delivered to 1Panel compose, each application's each service should by default carry:
labels:
createdBy: "Apps"
This is service-level default convention, not dependent on whether connected to bridge network; and should be included in validation script's mandatory check.
1Panel bridge network convention: If application belongs to bridge-type application needing connection to 1Panel public entry, reverse proxy chain or other external shared networks, compose must let corresponding service connect to at least one external network. Example:
The hard requirement here is "bridge-type application must connect to external network", not network name must be fixed as 1panel-network. 1panel-network is just default common/recommended name; if use other external network, should not be considered error. Validation script should prioritize checking "whether external network exists", not checking network name equals 1panel-network.
Multi-service DNS collision guard: If the primary service joins both 1panel-network and an internal network, do not leave dependency hostnames as generic redis, mongo, mysql, postgres, or db when those services are defined in the same compose. Use app-prefixed service names or explicit internal aliases. validate-v2.sh warns on this pattern because Docker DNS can resolve same-name services from the shared network before the intended internal service.
README store-style (default suggestion): Root README.md should by default organize into 1Panel store style description, not directly retain upstream technical README. Recommend at least clarify: installation method (source build/image), access port, data persistence, key environment variables, version differences and usage suggestions. Unless user explicitly indicates not needed, should be default delivery item.
Update README safety note: For non-trivial updates, include backup scope, direct-upgrade support, required intermediate versions, migration wait/log hints, and any changed image/database/cache dependency.