Imported from okxlin/1panel-app-adapter (
SKILL.md). Install upstream withnpx skills add okxlin/1panel-app-adapter. Copyright stays with the author.
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, andreferences/lifecycle-safety.md, then record the preflight decision before scaffolding. Stop forplatform_stack_terminal; stop forspecialized_conditionaluntil every named prerequisite is proven.--out-diris a parent: for example, runbash 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 containdata.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 synthesizelocalhostor127.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, andreferences/lifecycle-safety.md. Runpython3 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, andreferences/lifecycle-safety.md. Runbash 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 validsource-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, andreferences/lifecycle-safety.md. Precheck the complete prepared input withpython3 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 withpython3 scripts/import-baota-app.py --input <baota-app-dir> --out-dir <out-dir> --version <exact-version> --validate --require-validate. Review every output asconverted_candidateagainst 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, andreferences/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, andreferences/lifecycle-safety.md; also readreferences/upgrade-maintenance.mdwhen several versions or an update are involved. Start withbash 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, andreferences/lifecycle-safety.mdbefore 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.
| Job | Command |
|---|---|
| New v2 scaffold | bash scripts/scaffold-v2.sh --app-key <key> --title <title> --image <image> --version <version> --out-dir <out-dir> --source-repository <url> --source-docker-docs <url> --source-compose-file <url> |
| AppSpec generation | python3 scripts/generate-from-appspec.py --spec <appspec.json> --out-dir <out-dir> --validate --require-validate |
| v1 migration | 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> |
| Single Baota precheck | python3 scripts/import-baota-app.py --input <baota-app-dir> --precheck-only --report <report.json> |
| Batch Baota precheck | python3 scripts/import-baota-app.py --input <apphub-dir> --batch --precheck-only --report <report.json> |
| Explicit-version Baota import | python3 scripts/import-baota-app.py --input <baota-app-dir> --out-dir <out-dir> --version <exact-version> --validate --require-validate --report <report.json> |
| Root metadata patch | python3 scripts/patch_root_data_yml.py <app-dir>/data.yml [app-key] [architectures] |
| Version metadata patch | python3 scripts/patch_version_data_yml.py <app-dir>/<version>/data.yml |
| Compose patch | python3 scripts/patch_compose_yml.py <app-dir>/<version>/docker-compose.yml [app-type] |
| Regenerate env sample | bash scripts/gen-env-sample.sh <app-dir>/<version>/data.yml <app-dir>/<version>/.env.sample |
| Backfill lifecycle scripts | bash scripts/finalize_runtime_scripts.sh <app-dir> <app-dir>/<version> |
| Apply a proven non-root directory owner | bash scripts/finalize_runtime_scripts.sh <app-dir> <app-dir>/<version> --dir-owner APP_DATA_DIR=<uid>:<gid>:0750 --replace-init |
| Normalize logo | bash scripts/normalize-logo.sh <app-dir>/logo.png |
| Baseline validation | bash scripts/validate-v2.sh --dir <app-dir> [--version <version>] |
| Delivery validation | bash scripts/validate-v2.sh --dir <app-dir> [--version <version>] --strict-store --i18n-mode strict --source-evidence-mode required --require-delivery-evidence |
Completion Gates
- 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 uniquecontainer_namebased 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
ordinary_candidateorspecialized_conditionalwith satisfiable named prerequisites. - 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 formenvKey,label, ortypechanges, re-review itsdefault,rule, andvalues; keep every non-empty, non-secret, Compose-consumed form default consistent with.env.sampleas defined inreferences/lifecycle-safety.md. Treat validated form values as literal data during template generation, never as regular-expression or replacement-program input.required: trueor a genericparamComplexityrule 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 singleimageEvidencecovers 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
runtimeIdentityto its selectedimages[]entry (or legacyimageEvidence) with numericstartupUid,startupGid,steadyStateUid,steadyStateGid, an exact-version HTTPSsource, andwritableBindOwner. Usehost-initonly with generated--dir-owner/--fixed-dir-ownercoverage for every bind,image-managedonly with a separate HTTPSownerEvidence, androot-runtimeonly whensteadyStateUidis0. 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.sampleclosure. - 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
.envas untrusted data in lifecycle scripts. Neversourceorevalit; parse only exact known keys, strip quotes, validate values, and resolve relative paths from the app root. - Complete the
references/lifecycle-safety.mdpath 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 anAPP_DATA_DIRform unless a selectable host path is actually required. Derive startup and steady-state runtime UID/GID separately from the published OCI configuration, Composeuser, 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-ownerhelper 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-created0755bind 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, anduninstall.shexist and retain executable mode in the delivered tree. Run baseline validation first, thenbash 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 useedit: falseunless an idempotent reconciliation or migration applies later edits to the real persisted consumer. Do not expose a control that only changes.envwhile 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.
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 relaxedsecurity_optsettings. 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.ymlfield hierarchy under v2 structure is very stable; this skill'svalidate-v2.sh --strict-storeperforms strict validation according to this.
- Language codes: Official write as
zh-Hant(note uppercase H); old writezh-hantis only for compatibility, recommend unified replacement.
Application-level: apps/<app>/data.yml
- Top-level only allows:
name/tags/title/description/additionalProperties - Top-level
typeappearing: WARN only (historical/non-standard field; official regular write doesn't place at top level) architectures: Must be inadditionalProperties.architectures(100% present in official samples)
strict-store additionalProperties required (missing = FAIL):
key/name/tags/type/website/document/architecturesgithub/shortDescZh/shortDescEn/crossVersionUpdate/limit- (Others like
recommend/description/memoryRequiredcan maintain WARN by occurrence rate)
Version-level: apps/<app>/<ver|latest|stable>/data.yml
- Top-level only allows:
additionalProperties- Can be
nullorobject(but under strict-store, recommend object with required fields filled)
- Can be
formFieldsmust be inadditionalProperties.formFields(top-levelformFieldsnot allowed)- Prohibit
architectures: Version-leveldata.yml(same level asdocker-compose.yml) should not havearchitectures(neither top-level nor insideadditionalProperties)
Convention supplement (this skill's default artifact style):
- root
data.ymltop-leveldescriptionuses single-line string (not map).- root
data.ymladditionalProperties.shortDescusesshortDescZh/shortDescEn(not map).additionalProperties.descriptionuses i18n map, must complete 8 languages:en/zh/zh-Hant/ja/ko/ru/ms/pt-br.- root
data.ymluses hierarchical structure: top-leveltagsandadditionalProperties.tagsboth exist and are semantically consistent (allow redundant expression).- Content consistency (strong constraint): root
data.ymltitle:, the following top-leveldescription:, andadditionalProperties.shortDescZh:must be the same short text (try to be one sentence).- Translation constraint (strong constraint):
additionalProperties.descriptionmust be the multilingual translation of the aboveshortDescZh(not repetition of project name/title).architectures(only rootdata.yml) represents Docker image supported architecture list; should be inadditionalProperties.architectures, using hierarchical array (e.g.,- amd64/- arm64); if cannot reliably obtain (e.g., no manifest info/offline), default only fill- amd64.
formFields Structure Facts (version-level)
Location: additionalProperties.formFields: [ ... ]
formFields[] item common fields
envKey(required)type(required)required(required)default(common)rule(optional; common set below)labelZh/labelEn(high-frequency; recommend providing both)label(multi-language map, high-frequency)child(appears whentype: apps)
formFields[].type allowed set (based on official dev/apps real samples)
numberpasswordselecttextappsservice
Notes:
- Official repo has both patterns:
type: apps+child.type: service- Direct
type: service
- Therefore validator should allow
serviceinformFields[].type, but adapted artifacts can preferapps + child.serviceto express "dependency selection → service instance selection" two-step semantics.
rule common set (for this skill validation)
paramPort(most common, forPANEL_APP_PORT_*)paramExtUrlparamCommonparamComplexity
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:
editis bool in DTO (default equivalent to false), but official library explicitly writesedit: truefor many fields.- Choose
editfrom the value lifecycle, not from a blanket editable default:- Use
edit: trueonly when the steady-state consumer reads the changed value directly, or an idempotent reconciliation or migration safely applies it to persisted state. - Use
edit: falsefor a value generated once, identity-bearing configuration, panel injection, or any value whose persisted consumer is intentionally not regenerated. Typical injection keys includePANEL_DB_*,PANEL_REDIS_*, andPANEL_MINIO_*. - Dependency selection fields (
type: apps/service) may useedit: trueonly when the linked-resource lifecycle supports changing the selection; enumeration alone is not migration evidence.
- Use
- Validation strategy:
validate-v2.sh --strict-storewill WARN when a required non-selector field lacks an expliciteditdecision (not FAIL, to avoid rejecting official historical output). Resolve the warning from the consumer lifecycle; do not silence it mechanically withedit: true.
Database / Redis Dependency Injection (Panel Fixed envKey)
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.
Common fixed envKeys (recommend using as needed):
- Database:
PANEL_DB_TYPE,PANEL_DB_HOST,PANEL_DB_NAME,PANEL_DB_USER,PANEL_DB_USER_PASSWORD - Redis:
REDIS_HOST,REDIS_PORT,PANEL_REDIS_ROOT_PASSWORD,REDIS_DB
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: appspluschild.type: service. - For single-step selectors such as Redis service reuse, prefer
type: servicewith the dependencykey(for examplekey: 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(includinglabelEn/labelZh+labelmap, includeszh-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.
mysqlandlocalmysqlare distinct values and must be verified independently through/apps/services/mysqland/apps/services/localmysql; do not advertise one because the other works. version/data.ymlusestype: apps+child.type: serviceto injectPANEL_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 requestsPANEL_DB_NAME,PANEL_DB_USER, andPANEL_DB_USER_PASSWORD, require install and upgrade evidence that the host envKey is present in theservicespayload and the installed app reportslinkDB: trueor the expectedresourceKeys; 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_HOSTfromtype: texttotype: serviceis usually upgrade-safe because existing.envvalues 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 inscripts/upgrade.shwhen possible. - PostgreSQL-only rule: if the app relies on panel-side PostgreSQL provisioning (
CreateDatabasein 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 withUser 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_KEYexpects Laravel-compatible key material, while MastodonACTIVE_RECORD_*,SECRET_KEY_BASE,OTP_SECRET, andVAPID_*have upstream-specific generator commands and formats. - When official docs expose a generator helper, prefer
scripts/init.sh/scripts/upgrade.shto generate or normalize those values from the official image/helper command instead of shipping a fixed sample secret indata.ymlor trusting a generic panel-generated random string. - If
scripts/init.shorscripts/upgrade.shreplaces 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 rotateAPP_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 aports:block. Keep the install-form envKey asPANEL_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.1inside the panel container. docker-compose.ymlif application usesDATABASE_*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
servicesobject; manually injecting onlyparams.REDIS_HOST=...orparams.PANEL_DB_HOST=...is not enough evidence that the packaged UI selector works.
- version
data.ymlformFields[].labelshould keep bothlabelZh/labelEnandlabelmap (compatible with different repositories/versions).- volumes host paths default prefer falling under
./data/*subdirectories (e.g.,./cachenormalizes 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.limitset to1.- Recommended write (compatible with 1Panel scenario):
And keep in service:volumes: zeroclaw-data: name: zeroclaw-datavolumes: - 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
--volumesonly 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-depsto 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 existsPANEL_APP_PORT_HTTPS/SSH/API/...). - Corresponding
formFieldsrule basically fixed as:type: numberrequired: truerule: paramPort(official almost always uses it)defaultis specific port number (1..65535)
docker-compose.ymlusually 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 userule: 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.yml usually 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/Zas 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.
depends_on / External Dependencies → apps/service Injection
Official library has both patterns:
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.ymlformFieldswithtype: select(e.g., values: true/false or debug/info/warn/error), avoid free text. - Example:
ZEROCLAW_ALLOW_PUBLIC_BINDswitch should be select (true/false). values[].labelmust always be pure string, prohibit multi-language map (strong constraint):- 1Panel backend will deserialize
formFields[].values[].labeltostring; 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 withvalues:, not just booleans.
- 1Panel backend will deserialize
- ❌ Wrong:
values: - label: en: 'True' zh: '是' value: "true" - ✅ Correct:
values: - label: "true" value: "true"
- If compose has
-
Boolean text values label must add quotes: When
values[].labelistrue/false, must write as string ("true"/"false"), avoid YAML parsing as boolean causing frontend/panel issues.- ❌ Wrong:
label: true - ✅ Correct:
label: "true"
- ❌ Wrong:
-
compose variable closure (strong constraint):
docker-compose.ymlvariables${VAR}/${VAR:-default}/${VAR?msg}etc., by default must find corresponding declaration in versiondata.ymlformFields.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_PORTprefix 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_PORTnaming, should map in compose:HOST_PORT=${PANEL_APP_PORT_HTTP}(or directly use${PANEL_APP_PORT_HTTP}as ports left side).
- 1Panel official convention:
-
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:image: "lscr.io/linuxserver/joplin:latest" image: "${ZEROCLAW_IMAGE}" image: "ghcr.io/org/app@sha256:..."
-
compose environment variable write prioritize
KEY=VALUElist (strong constraint, adaptation artifact requirement):- Official library
environment:has both list-style (- KEY=...) and map-style (KEY: ...). - For stable diff/migration and avoiding YAML edge parsing: our adaptation artifacts force list-style.
- Therefore: adaptation artifact's
docker-compose.ymlenvironment:must use list write:environment: - API_KEY=${API_KEY:-} - PROVIDER=${PROVIDER} - 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_KEYandXXX_API_KEYsynonym variable names, adaptation should only expose one main field (usually use upstream defaultAPI_KEY), another in compose comment form (don't inject two keys simultaneously, avoid ambiguity). - Avoid map write:
environment: API_KEY: ${API_KEY}
- Official library
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 exampleAPP_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 isDATA_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.shorscripts/upgrade.shneeds a persistence-path variable, derive the app root from the script location, read<app-root>/.envas 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./datahandling. .env.sampleis a standalone-compose convenience file, not the source of truth for 1Panel runtime parameters. 1Panel installs render their own.envfrom form values and platform injection. Do not reverse-fill.env.samplefrom install-time state, and do not make packaged apps depend onenv_file: ./.env.samplefor 1Panel-only runtime behavior.- If a package needs an additional runtime defaults file such as
dify.env, treat it as a packaged pre-./.envlayer: document that role clearly, keep user-specific secret samples out of it where possible, and rely on generated./.envvalues or explicit compose env mappings to supply real install-time secrets. - For secret-like form fields, do not combine
random: truewith 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.ymlreferences${CONTAINER_NAME}, its.env.samplevalue must be non-empty and deterministic:CONTAINER_NAME=<app-key>-compose-check. Keep it out offormFields; 1Panel still replaces it at install time. Validate the exact sample withdocker compose --env-file .env.sample configand 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
name/titlefields. - If the image registry provides both
latestand numbered release tags, keep the movinglatestversion 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-networkand 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>-redisor 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
deniedresponse as proof that the package is private. Followreferences/source-policy.md: verify an anonymous manifest token, then pull with a temporary emptyDOCKER_CONFIGwithout changing the user's registry login state. Local cache success alone is not fresh-install evidence.
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
rootto repair bind-mount permissions and then drops privileges withsetpriv,gosu,su-exec, or similar, also setHOME,USER, andLOGNAMEfor the target application user beforeexec. Some runtimes and package managers keep using/rootafter UID/GID changes unless the environment is corrected; for example,pnpmcan fail withEACCESwhile 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/postgresqlso futurepg_upgrade --linkflows do not cross mount boundaries. If you customizePGDATAor keep the older/var/lib/postgresql/datapath, 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.shreceives 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_successfullyand 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.sqlcan 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
200do 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|offwarn: warn but continue whensource-evidence.jsonis missing or invalid (default)required: requiresource-evidence.json; with--strict-store, also require application-license evidence plus hash-bound asset and redistribution-material delivery evidenceoff: skip source evidence checks
--i18n-mode off|warn|strictoff: disable translation quality check (only structure validation)warn: only warning (default)strict: fail on rule hit (recommend CI use)
--i18n-scope description|labels|alldescription: only validate rootadditionalProperties.descriptionlabels: only validate versionformFields[].labelall: both (default)
--i18n-allow-english-labels <CSV>- Short label English whitelist (e.g.,
API,URL,ID,OAuth,JWT), avoid mis-killing technical words.
- Short label English whitelist (e.g.,
labelsscope supplement: IfformFields[]only haslabelEn/labelZhand missinglabel:multi-language map,validate-v2.shwill now give WARN, and clarify versionformFields.labelexpected to complete 8 languages:en/zh/zh-Hant/ja/ko/ru/ms/pt-br.
Default strategy:
descriptionmore strict (prevent whole sentence English pseudo-translation)formFields.labelhierarchical 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.
Output Contract
Delivery should at least clarify:
- Artifact path (usually
artifacts/1panel-apps/<app-key>) - Generated/migrated version directory
- Which official sources Docker installation details come from
- The completed path and mount ledger, runtime identity evidence, secret/file contracts, and lifecycle test evidence
- Which cited files are delivered in the AppStore package and which evidence caches remain run-only
- Remaining warnings, assumptions, manual confirmation items
- Local test landing:
/opt/1panel/resource/apps/local/<app-key>
Notes
- Always use
1panelnaming (don't writeonepanel). - Rules must be backed by authoritative sources; unverified assumptions should not be elevated to MUST.
- Submission workflow baseline reference official wiki: https://github.com/1Panel-dev/appstore/wiki/%E5%A6%82%E4%BD%95%E6%8F%90%E4%BA%A4%E8%87%AA%E5%B7%B1%E6%83%B3%E8%A6%81%E7%9A%84%E5%BA%94%E7%94%A8
data.ymltags must come from observed store set (1Panel-dev/appstore@dev/apps). Allowed tags:Tool,DevOps,AI,Database,Website,Middleware,Security,Runtime,Media,Storage,Game,CRM,Email,Server,BI.scaffold-v2.shsupports auto-tagging (and supports--tagoverride).- Default fallback tag is
Tool(no longer useDocker). - Default logo:
- This repository's project-authored source is
assets/default-logo.svg; its terms are inassets/default-logo.LICENSE.txt(MIT, copyright 2026 okxlin). - The deterministically rendered
assets/default-logo.pngis 180x180 and has SHA-256a8f604f27c3451536301f1a4ca7ac5ae8c479312a225c42c4dc0edda2a20bf76. scaffold-v2.shandgenerate-from-appspec.pycopy 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.
- This repository's project-authored source is
- Logo normalization suggestion: Before delivery, prioritize unifying
<app>/logo.pngto 180x180 PNG; processing should maintain original logo ratio, don't stretch, don't compress, don't deform. If original exceeds180x180, only do proportional shrink; if original is smaller, don't force enlarge. Finally center overlay logo onto180x180transparent canvas. If want to balance repository size and store loading efficiency, recommend compressing to no more than 10KB. Can directly use:bash scripts/normalize-logo.sh <logo.png>. - Compose top-level
versionhandling: Delivered to 1Paneldocker-compose.ymlshould remove top-levelversion:field (e.g.,version: '3.8'), avoid deprecated/ignored warnings in 1Panel / Docker Compose logs. Adaptation should directly start fromservices:organizing compose content, unless encountering special scenarios requiring old parser. - Service-level
createdBylabel convention: Delivered to 1Panel compose, each application's each service should by default carry:
This is service-level default convention, not dependent on whether connected to bridge network; and should be included in validation script's mandatory check.labels: createdBy: "Apps" - 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", nservices: app: networks: - some-external-network networks: some-external-network: external: true
Truncated - read the full file at https://github.com/okxlin/1panel-app-adapter/blob/1af3a585d413bbafccd1231512ad6f07839fe8b1/SKILL.md.