AI AdminPanel Documentation

AAP Template Spec v2 — Authoring Guide

Overview

Templates define how applications are deployed on AI AdminPanel. Each template is a YAML file that describes the Docker Compose configuration, user-configurable variables, resource requirements, and routing rules.

This guide covers the v2 template specification. All curated templates in the catalog follow this exact format.

Template Structure

A template YAML file has three sections:

  1. Top-level envelopeapiVersion and kind (always v2 and Template)
  2. Metadata — name, description, category, tags, icon
  3. Spec — compose definition, variables, resource requirements, domain routing

Here is a complete minimal example:

apiVersion: v2
kind: Template
metadata:
  name: my-app
  displayName: My App
  description: "A simple web application with persistent storage."
  category: webapps
  icon: ""
  tags: [web]
spec:
  domains:
    - port: 3000
  minResources:
    cpuCores: 1
    memoryMB: 256
  compose: |
    services:
      app:
        image: myapp/myapp:latest
        expose:
          - "3000"
        volumes:
          - app_data:/app/data
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
          interval: 30s
          timeout: 10s
          retries: 3
        restart: unless-stopped
    volumes:
      app_data:

Full YAML Reference

Metadata Fields

FieldTypeRequiredDescription
apiVersionstringyesMust be "v2"
kindstringyesMust be "Template"
metadata.namestringyesURL-safe slug (e.g., "my-app"). Used as the unique identifier.
metadata.displayNamestringyesHuman-readable name shown in the catalog UI
metadata.descriptionstringyes2-3 sentence marketing description
metadata.categorystringyesOne of: ai, webapps, databases, devtools
metadata.iconstringnoInline SVG string, or empty for default icon
metadata.tagsstring[]noSearch and filter tags (e.g., [web, database, llm])
metadata.trendingboolnoShow in the trending section of the catalog
metadata.comingSoonboolnoShow as coming soon (not deployable)

Spec Fields

FieldTypeRequiredDescription
spec.composestringyes*Docker Compose YAML (as a multiline string)
spec.imagestringyes*Docker image for single-container shorthand
spec.domainsDomainSpec[]recommendedPort-to-domain routing declarations
spec.minResourcesobjectyes{cpuCores: int, memoryMB: int}
spec.securityProfilestringno"secure", "advanced", or "raw" (default: no restriction)
spec.recommendedVersionstringnoLast tested image tag (informational, shown in UI)
spec.variablesVariable[]noUser-configurable parameters
spec.ai_managedboolnoMarks the service as AI-managed (shows AI badge in catalog)
spec.gpu_requiredboolnoIndicates GPU hardware is required
spec.recommended_vram_mbintnoRecommended GPU VRAM in megabytes

*Either compose or image must be provided. If only image is set, the platform generates a minimal compose wrapper automatically.

Variable Fields

FieldTypeRequiredDescription
namestringyesEnvironment variable name (UPPER_SNAKE_CASE)
displayNamestringyesLabel shown in the deployment form
typestringyes"string", "integer", "boolean", "password", or "select"
defaultstringyesDefault value (use "" for empty)
requiredboolnoMust be filled before deploy (default: false)
descriptionstringnoHelp text shown below the input field
lockedboolnoRead-only when security profile is secure
hiddenboolnoOnly shown in Advanced mode (hidden from basic deploy form)
optionsSelectOption[]noFor select type only: [{value: "x", label: "X Label"}]

DomainSpec Fields

FieldTypeRequiredDescription
portintyesContainer port to expose (1-65535)
protocolstringno"https" (default) or "http"

System Variables

System variables are auto-resolved by the platform at deploy time. Use them in your compose YAML with ${AAP_*} syntax. You never define these in the variables section — they are built-in.

Auto-Generated Credentials

PatternGeneratesLengthUse Case
${AAP_PASSWORD_<ID>}Random alphanumeric password24 charsDatabase passwords, admin passwords
${AAP_SECRET_<ID>}Random hex string64 charsJWT secrets, API keys, encryption keys
${AAP_BASE64_<ID>}Base64-encoded random bytes44 charsCredential encryption keys

Consistency rule: The same <ID> resolves to the same value everywhere in one template. For example, ${AAP_PASSWORD_DB} used in both the app service and the database service will produce the same password.

IDs must be: UPPER_SNAKE_CASE — e.g., DB, ADMIN, JWT, SESSION, ROOT, CREDS

Domain Variables

These are resolved at deploy time based on the service name and panel configuration:

VariableResolves ToExample
${AAP_FQDN}Fully qualified domain namemyapp.panel.example.com
${AAP_URL}Full URL with protocolhttps://myapp.panel.example.com
${AAP_SERVICE_NAME}Service slug namemyapp

Quick Reference: Which Variable to Use

NeedVariable
Database password${AAP_PASSWORD_DB}
Admin password${AAP_PASSWORD_ADMIN}
JWT / session secret${AAP_SECRET_JWT}
Encryption key${AAP_SECRET_CREDS} or ${AAP_BASE64_CREDS}
Application URL (with https://)${AAP_URL}
Domain name only (no protocol)${AAP_FQDN}

Domain Routing

Declare which port your service listens on:

spec:
  domains:
    - port: 3000

The platform automatically configures Traefik to route https://{service}.{panel-domain} to container:3000. Users never need to enter URLs or configure reverse proxy rules.

For multi-port services (rare), declare multiple domains:

spec:
  domains:
    - port: 3000  # Main web UI
    - port: 8080  # API endpoint

Tip: You do not need to add Traefik labels or configure SSL certificates. The platform handles all routing, TLS termination, and certificate provisioning automatically.


Compose Requirements

When writing the spec.compose section, follow these rules:

Must Do

  • Use expose: instead of ports: — Traefik handles external access. Never bind to host ports.
  • Include a healthcheck: — The platform uses this to detect when the service is ready.
  • Use named volumes for persistence — not bind mounts.
  • Use restart: unless-stopped on all services.
  • Use system variables for URLs — never hardcode domains (${AAP_URL}, ${AAP_FQDN}).
  • Use ${AAP_PASSWORD_*} / ${AAP_SECRET_*} for credentials — never hardcode secrets.

Must Not

  • Do not use ports: — this will conflict with Traefik routing.
  • Do not hardcode URLs or domains — they change per deployment.
  • Do not use bind mounts (./data:/app/data) — use named volumes.
  • Do not include Traefik labels — the platform adds them automatically.
  • Do not set network_mode: host — this breaks container isolation.

Health Check Examples

HTTP health check (most common):

healthcheck:
  test: ["CMD-SHELL", "curl -sf http://localhost:3000/ || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 30s

PostgreSQL readiness check:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U myuser"]
  interval: 10s
  timeout: 5s
  retries: 5

TCP port check (when no HTTP endpoint exists):

healthcheck:
  test: ["CMD-SHELL", "nc -z localhost 6379 || exit 1"]
  interval: 10s
  timeout: 5s
  retries: 3

Security Profiles

The securityProfile field controls how the platform handles template variable overrides:

ProfileBehavior
secureDefault for curated templates. Variables marked locked: true cannot be changed by the user — they always use the default value.
advancedAll variables are editable. May request specific Linux capabilities or device access.
rawNo restrictions. Full Docker access. Use with caution.

When authoring templates, prefer secure and lock any variables that could compromise security if changed (e.g., bind addresses, exec policies).


Variable Types in Detail

String

Free-text input. Validated against control character injection.

- name: APP_TITLE
  displayName: Application Title
  type: string
  default: "My Application"
  required: false

Integer

Numeric-only input. Only digits are accepted.

- name: WORKER_COUNT
  displayName: Worker Threads
  type: integer
  default: "4"
  required: false

Boolean

Toggle switch. Value must be exactly "true" or "false".

- name: ENABLE_REGISTRATION
  displayName: Allow User Registration
  type: boolean
  default: "true"
  required: false

Password

Masked input field. Validated against YAML injection. Good for API keys and tokens that users provide.

- name: OPENAI_API_KEY
  displayName: OpenAI API Key
  type: password
  default: ""
  required: false
  description: "Your OpenAI API key. Get one at https://platform.openai.com/api-keys"

Note: For auto-generated passwords (database credentials, admin passwords), use system variables (${AAP_PASSWORD_*}) instead of a password variable. System variables are generated automatically — the user never sees or enters them.

Select

Dropdown with predefined options. The submitted value is validated against the options list.

- name: DEPLOYMENT_PROFILE
  displayName: Deployment Profile
  type: select
  default: "solo"
  required: true
  description: "Resource allocation profile"
  options:
    - value: solo
      label: "Solo Dev — 1 CPU, 512MB RAM"
    - value: team
      label: "Team — 2 CPU, 2GB RAM"
    - value: production
      label: "Production — 4 CPU, 4GB RAM"

Examples

Minimal: Single-Container Service

The simplest possible template — one container, one volume, one port.

apiVersion: v2
kind: Template
metadata:
  name: uptime-kuma
  displayName: Uptime Kuma
  description: "Self-hosted uptime monitoring tool with a beautiful status page."
  category: devtools
  icon: ""
  tags: [monitoring, uptime, status-page]
spec:
  domains:
    - port: 3001
  minResources:
    cpuCores: 1
    memoryMB: 128
  recommendedVersion: "1.23.16"
  variables:
    - name: VERSION
      displayName: Uptime Kuma Version
      type: string
      default: "latest"
      required: false
      hidden: true
  compose: |
    services:
      uptime-kuma:
        image: louislam/uptime-kuma:${VERSION}
        expose:
          - "3001"
        volumes:
          - uptime_kuma_data:/app/data
        healthcheck:
          test: ["CMD-SHELL", "curl -sf http://localhost:3001/ || exit 1"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 30s
    volumes:
      uptime_kuma_data:

Multi-Service: App + Database

A web application with a PostgreSQL database. Uses system variables for credential sharing between services.

apiVersion: v2
kind: Template
metadata:
  name: my-webapp
  displayName: My Web App
  description: "Web app with PostgreSQL database. Zero-config deployment with auto-generated credentials."
  category: webapps
  icon: ""
  tags: [web, database]
spec:
  domains:
    - port: 3000
  minResources:
    cpuCores: 1
    memoryMB: 512
  variables:
    - name: VERSION
      displayName: App Version
      type: string
      default: "latest"
      required: false
      hidden: true
  compose: |
    services:
      app:
        image: myapp/webapp:${VERSION}
        environment:
          DATABASE_URL: postgresql://app:${AAP_PASSWORD_DB}@db:5432/myapp
          APP_URL: ${AAP_URL}
          SESSION_SECRET: ${AAP_SECRET_SESSION}
        expose:
          - "3000"
        depends_on:
          db:
            condition: service_healthy
        healthcheck:
          test: ["CMD-SHELL", "curl -sf http://localhost:3000/health || exit 1"]
          interval: 30s
          timeout: 10s
          retries: 3
        restart: unless-stopped
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_USER: app
          POSTGRES_PASSWORD: ${AAP_PASSWORD_DB}
          POSTGRES_DB: myapp
        volumes:
          - db_data:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U app"]
          interval: 10s
          timeout: 5s
          retries: 5
        restart: unless-stopped
    volumes:
      db_data:

Note how ${AAP_PASSWORD_DB} appears in both the app and db services. The platform generates the password once and substitutes the same value in both places.

AI Service with User-Provided API Keys

An AI application where the user provides their own API key during deployment.

apiVersion: v2
kind: Template
metadata:
  name: ai-chat
  displayName: AI Chat
  description: "AI chat application with configurable LLM provider. Bring your own API key."
  category: ai
  icon: ""
  tags: [ai, chat, llm]
spec:
  ai_managed: true
  domains:
    - port: 3000
  minResources:
    cpuCores: 1
    memoryMB: 512
  variables:
    - name: OPENAI_API_KEY
      displayName: OpenAI API Key
      type: password
      default: ""
      required: false
      description: "Your OpenAI API key. Get one at https://platform.openai.com/api-keys"
    - name: MODEL
      displayName: Default Model
      type: select
      default: "gpt-4o"
      required: true
      options:
        - value: gpt-4o
          label: "GPT-4o (recommended)"
        - value: gpt-4o-mini
          label: "GPT-4o Mini (faster, cheaper)"
  compose: |
    services:
      chat:
        image: ai-chat/app:latest
        environment:
          OPENAI_API_KEY: ${OPENAI_API_KEY}
          DEFAULT_MODEL: ${MODEL}
          APP_URL: ${AAP_URL}
          JWT_SECRET: ${AAP_SECRET_JWT}
        expose:
          - "3000"
        healthcheck:
          test: ["CMD-SHELL", "curl -sf http://localhost:3000/health || exit 1"]
          interval: 30s
          timeout: 10s
          retries: 3
        restart: unless-stopped

Using the Image Shorthand

For the simplest single-container services, you can use spec.image instead of spec.compose. The platform generates a minimal compose wrapper automatically.

apiVersion: v2
kind: Template
metadata:
  name: simple-app
  displayName: Simple App
  description: "A stateless web application."
  category: webapps
  icon: ""
  tags: [web]
spec:
  image: myapp/simple:latest
  domains:
    - port: 8080
  minResources:
    cpuCores: 1
    memoryMB: 128

This is equivalent to writing a full compose spec with a single service. Use the compose field instead when you need volumes, health checks, environment variables, or multiple services.


Input Validation and Security

The platform performs strict validation on all user-provided variable values before substituting them into compose YAML:

  • String values are rejected if they contain control characters or newlines
  • Integer values must match ^\d+$
  • Boolean values must be exactly "true" or "false"
  • Password values are rejected if they contain control characters or YAML structure characters (: , #, - )
  • Select values are validated against the declared options list

This prevents YAML injection attacks where a malicious variable value could alter the compose structure.


Submitting to the Catalog

To contribute a template to the curated catalog:

  1. Fork the repository
  2. Create your template YAML in internal/templates/catalog/{category}/
  3. Run go test ./internal/templates/... to validate the template loads and passes all checks
  4. Submit a pull request

All templates in the catalog are embedded into the panel binary at build time and seeded into the database on startup.