Container-Vorlagen für die mStudio Container-Vorlagen-Funktion.
Jedes Template liegt in einem eigenen Ordner und besteht aus drei Pflichtdateien, optional ergänzt um Screenshots:
<template-name>/
├── docker-compose.yml # Docker Compose Konfiguration
├── manifest.yaml # Metadaten, Inputs und Konfiguration
├── icon.svg # Icon (muss immer icon.svg heißen)
├── background.jpg # optional: Hintergrundbild (siehe screenshots)
└── screenshot-*.jpg # optional: Screenshot der Oberfläche (siehe screenshots)
Standard Docker Compose Datei mit den Services, Volumes und Netzwerken des Templates. Umgebungsvariablen werden über ${VARIABLE} referenziert und durch die im Manifest definierten User- und System-Inputs befüllt.
Beschreibt das Template mit folgenden Feldern. Die maschinenlesbare, per CI validierte Definition (Typen, Pflichtfelder, erlaubte Werte) steht in manifest.schema.json.
| Feld | Beschreibung |
|---|---|
manifestVersion |
Version der Manifest-Syntax (aktuell 1.0) |
name |
Menschenlesbarer Name des Templates (mehrsprachig) |
version |
Angezeigte Version (z.B. "2.x.x") |
tagline |
Kurzbeschreibung (mehrsprachig) |
description |
Beschreibung (mehrsprachig, Markdown, ~175 Wörter) |
developer |
Name des Entwicklers/Herstellers |
website |
Projekt-Website |
repository |
Source-Code Repository |
support |
Support-URL |
license |
Lizenz der Software, besteht aus name und link. |
categories |
Kategorien: productivity, development, database, ai, security, monitoring, communication, media. database ist keine offizielle Store-Kategorie, sondern nur intern für Template-Listen im „Datenbanken"-Bereich |
screenshots |
Optionale Katalog-Screenshots: Hintergrundbild, Screenshot und mehrsprachige Bildüberschrift |
domains |
Domain-Zuordnung zu Services und Ports; Map mit dem purpose als Schlüssel und Referenz (z.B. in help) |
userInputs |
Vom Benutzer konfigurierte Werte |
systemInputs |
Automatisch vom System generierte Werte (Passwörter, Tokens) |
deliveryBoxes |
Delivery-Boxen für den Mailversand; die Zugangsdaten legt das System an |
type |
Gibt an, ob das Template als eigenständige Anwendung in einem neuen Stack (standalone) oder als Baustein in einen bestehenden Stack (component) deployt wird |
help |
Optionale Kontext-Hilfe nach dem Deployment: technische Details (technicalDetails) und Hinweise (alerts) |
Optionale Katalog-Screenshots. Jeder Eintrag besteht aus einem dekorativen Hintergrundbild (bg), dem darauf platzierten Screenshot der Anwendungsoberfläche (screenshot) und einer mehrsprachigen Überschrift (text), die über dem Screenshot angezeigt wird.
screenshots:
- bg: background.jpg
screenshot: screenshot-workflow-editor.jpg
text:
de: Workflows im visuellen Editor per Drag-and-drop erstellen
en: Building workflows by drag and drop in the visual editorBeide Bilder liegen als Datei im Template-Ordner; im Manifest steht nur der Dateiname, keine Pfadangabe. Es gelten folgende Regeln, die per CI geprüft werden:
| Regel | |
|---|---|
| Format | jpg, jpeg, png oder webp |
| Breite | mindestens 1500 px (beide Bilder) |
bg |
Seitenverhältnis exakt 3:2 (z.B. 3000×2000) |
screenshot |
kein festes Seitenverhältnis |
Für die Bildüberschrift text gilt zusätzlich eine Längenvorgabe, die der Validator nicht erzwingt: höchstens 70 Zeichen je Sprache, ein Satz ohne Punkt am Ende, kürzer ist besser.
Das Hintergrundbild wird nicht von Hand gebaut, sondern aus den Farben des Templates erzeugt:
pnpm gen:background bugsink # Farben aus dem ersten Screenshot
pnpm gen:background bugsink --from icon # Farben aus icon.svg
pnpm gen:background --all --dry-run # nur die Paletten anzeigenDas Ergebnis ist ein weicher Verlauf als <template>/background.jpg mit 3000×2000 px. Der Verlauf ist deterministisch aus dem Templatenamen abgeleitet, dasselbe Template erhält also immer dieselbe Bühne. Ein vorhandenes background.jpg wird nur mit --force überschrieben.
Lokal prüfen:
pnpm validateVerknüpft einen userInput (Host) mit einem Service und Port für automatisches Domain-Routing. domains ist eine Map, deren Schlüssel der purpose ist — darüber wird die zugewiesene Domain an anderer Stelle referenziert (z.B. in help via ${domain.<purpose>}). Bei einer einzelnen Domain ist der Schlüssel üblicherweise main; bei mehreren Domains je Eintrag ein eigener, sprechender Wert.
domains:
main:
userInput: HOST
service: n8n
port: 5678domains:
nextcloud:
userInput: HOST
service: nextcloud-euro-office
port: 80
eurooffice:
userInput: EUROOFFICE_HOST
service: eurooffice
port: 80userInputs ist eine Liste von Werten, die der Benutzer bei der Installation eingibt. Jeder Eintrag kann folgende Felder haben:
| Feld | Pflicht | Beschreibung |
|---|---|---|
name |
ja | Technischer Schlüssel; in der docker-compose.yml als ${name} referenzierbar |
label |
ja | Mehrsprachiger Anzeigename des Feldes (de/en) für die Oberfläche |
dataType |
nein | Datentyp: text, number, boolean oder select (Standard: text) |
required |
ja | Ob der Wert zwingend ausgefüllt werden muss |
validationSchema |
ja | Validierung als JSON-Schema-String (siehe json-schema.org) |
format |
nein | Eingabeformat/Maskierung: email, password, url oder uri |
dataSource |
nein | Verknüpft den Input mit einer Systemquelle, z.B. ingress.paths (Domain) oder aiHosting.apiKey |
positionMeta |
nein | Platzierung im Installations-Assistenten: { step, index } (siehe unten) |
defaultValue |
nein | Vorbelegter Standardwert; kann Platzhalter enthalten (siehe unten) |
userInputs:
- name: "HOST"
label:
de: Domain
en: Domain
dataType: "text"
required: true
dataSource: "ingress.paths"
positionMeta: { step: "domain", index: 1 }
validationSchema: '{ "type": "string", "minLength": 1 }'
- name: "ADMIN_PASSWORD"
label:
de: Admin Passwort
en: Admin Password
dataType: "text"
format: "password"
required: true
positionMeta: { step: "adminUser", index: 1 }
validationSchema: '{ "type": "string", "minLength": 12 }'step ordnet den Input einem Schritt des Installations-Assistenten zu. Die Auswahl ist abschließend — für jeden Schritt pflegt das Frontend eine Übersetzung, neue Werte müssen dort zuerst angelegt werden:
| Step | Inhalt |
|---|---|
domain |
Domains des Stacks — alle Inputs mit dataSource: "ingress.paths" |
adminUser |
Zugangsdaten des Administrators: Benutzername, E-Mail-Adresse, Passwort, Token |
ai |
Anbindung an das mittwald AI Hosting: Endpunkt und API-Key |
common |
Alles Übrige, also templatespezifische Einstellungen |
index bestimmt die Reihenfolge innerhalb eines Steps und beginnt je Step bei 1.
defaultValue kann Platzhalter enthalten, die beim Öffnen des Installations-Assistenten mit Werten des angemeldeten Benutzers bzw. des Projekts vorbelegt werden. Der Benutzer kann den vorbelegten Wert anschließend überschreiben.
| Platzhalter | Wert |
|---|---|
${user.email} |
E-Mail-Adresse des angemeldeten Benutzers |
${user.username} |
Benutzername |
${user.firstName} |
Vorname |
${user.lastName} |
Nachname |
${user.fullName} |
Vor- und Nachname |
${aiHosting.llmEndpoint} |
Endpunkt-URL des mittwald AI Hostings |
userInputs:
- name: "ADMIN_EMAIL"
label:
de: Admin E-Mail-Adresse
en: Admin email address
positionMeta: { step: "adminUser", index: 1 }
defaultValue: "${user.email}"
format: "email"
required: true
validationSchema: '{ "type": "string", "format": "email" }'Sie werden nur zur Installationszeit aufgelöst und stehen — anders als die Platzhalter in help — danach nicht mehr zur Verfügung.
systemInputs sind Werte, die das System bei der Installation selbst erzeugt — Passwörter, Schlüssel und Tokens. Im Installations-Assistenten tauchen sie nicht auf.
| Feld | Pflicht | Beschreibung |
|---|---|---|
name |
ja | Technischer Schlüssel; in der docker-compose.yml als ${name} referenzierbar |
schema.kind |
ja | Art des Regelsatzes; für generierte Werte immer 1 |
schema.schema |
ja | Regelliste, aus der der Wert erzeugt und gegen die er validiert wird |
Erzeugt werden die Werte vom Password-Service auf Basis von @mittwald/password-tools-js. Es gibt keine getrennte Konfiguration für Generierung und Validierung — dieselbe Regelliste tut beides.
ruleType |
Felder | Bedeutung |
|---|---|---|
length |
min, max |
Zeichenzahl. min bestimmt zugleich, wie lang der erzeugte Wert wird |
charPool |
charPools, min, max |
Zählt Zeichen aus den genannten Pools: lowercase, uppercase, numbers, special, nonAscii |
char |
chars, min, max |
Dasselbe für eine explizit aufgezählte Zeichenmenge |
regex |
pattern, min, max |
Zählt die Treffer des Musters |
identifier ist überall optional und dient nur als Label.
Eine Regel mit min ist eine Untergrenze, keine Einschränkung: charPools: [numbers] mit min: 1 verlangt mindestens eine Ziffer, sagt über den Rest des Werts aber nichts. Ausgeschlossen wird ein Pool nur mit max: 0 — und nur das nimmt ihn auch aus dem Zeichenvorrat der Generierung heraus.
Fehlen min und max, gilt min: 1. Eine Regel ohne beides erzwingt das Zeichen also, statt es zu verbieten.
Datenbank-Passwörter landen oft in einer DSN wie postgres://user:${POSTGRES_PASSWORD}@postgres:5432/db. Zeichen wie @, # oder % zerlegen diese URL. Deshalb bekommt jedes Passwort den Sonderzeichen-Ausschluss:
systemInputs:
- name: "POSTGRES_PASSWORD"
schema:
kind: 1
schema:
- ruleType: length
min: 24
- identifier: numbers
ruleType: charPool
charPools:
- numbers
min: 1
- ruleType: regex
pattern: "[A-Z]"
min: 1
- ruleType: regex
pattern: "[a-z]"
min: 1
- identifier: noSpecial
ruleType: charPool
charPools:
- special
- nonAscii
max: 0Verlangt eine Anwendung einen Hex-String — erkennbar daran, dass die Upstream-Doku openssl rand -hex N nennt —, genügen zwei Ausschlüsse: ohne uppercase bleibt a-z0-9, ohne g-z bleibt [0-9a-f]. Die Länge wird auf min = max = 2 × N fixiert:
systemInputs:
- name: "CREDS_KEY" # openssl rand -hex 32
schema:
kind: 1
schema:
- ruleType: length
min: 64
max: 64
- identifier: hexOnly
ruleType: charPool
charPools:
- uppercase
- special
- nonAscii
max: 0
- identifier: hexOnlyLetters
ruleType: char
chars: "ghijklmnopqrstuvwxyz"
max: 0charmitmin≥ 1 zerstört die Generierung. Geforderte Einzelzeichen überschreiben intern Muster und Länge des erzeugten Werts. Er wird dann so lang wie diechars-Angabe, scheitert an derlength-Regel, und die Generierung läuft in den Timeout.charist nur mitmax: 0gefahrlos.- Base64 lässt sich nicht abbilden. Pools können nur vollständig verboten werden; einzelne Sonderzeichen wie
+und/gezielt zu erlauben, geht nicht. Wo die Dokuopenssl rand -base64 Nempfiehlt, ist ein alphanumerischer Wert gleicher Länge der richtige Ersatz. - Die Länge folgt der Doku, nicht dem Minimum.
openssl rand -base64 32ergibt 44 Zeichen,openssl rand -hex 32ergibt 64. Diese Zahl gehört inlength, auch wenn die Anwendung nominell weniger verlangt.
Versendet ein Template Mails, deklariert es dafür eine Delivery-Box statt SMTP-Zugangsdaten abzufragen. Das System legt die Box beim Deployment an und stellt die Zugangsdaten als Umgebungsvariablen bereit:
deliveryBoxes:
- purpose: mainDer purpose bestimmt den Namen der Variablen — aus main wird ${MW_DELIVERYBOX_MAIN_USERNAME} und ${MW_DELIVERYBOX_MAIN_PASSWORD}. Der Mailserver ist fest mail.agenturserver.de auf Port 587 mit STARTTLS:
services:
kimai:
environment:
- MAILER_URL=smtp://${MW_DELIVERYBOX_MAIN_USERNAME}:${MW_DELIVERYBOX_MAIN_PASSWORD}@mail.agenturserver.de?encryption=tlsDie Absenderadresse bleibt ein userInput (SMTP_FROM), weil sie eine inhaltliche Entscheidung ist und nicht zu den Zugangsdaten gehört.
Optionale Kontext-Hilfe, die dem Benutzer nach dem Deployment angezeigt wird. Sie besteht aus zwei Bereichen:
technicalDetails— Liste technischer Detailinfos (z.B. Zugangsdaten, Connection-String, Hostname/Port). Jeder Eintrag hat ein mehrsprachiges Labelkeyund einenvalue.alerts— Liste von Hinweisen/Warnungen. Pro Eintrag:status(danger,info,successoderwarning) sowie die mehrsprachigen Texteheadingundcontent; optional zusätzlich ein Link überlinkTextundlink.
Wichtig zu den Platzhaltern: Die Werte werden auch nach der Installation angezeigt. Es dürfen daher nur Platzhalter verwendet werden, die dann noch verfügbar sind. Da Umgebungsvariablen immer an einem einzelnen Service hängen (nicht am Stack), muss der Service-Name Teil des Platzhalters sein:
${<service>.env.NAME}— die UmgebungsvariableNAMEdes Services<service>(persistiert).${<service>.hostname}— der Laufzeit-Hostname des Services<service>.${domain.<purpose>}— der zugewiesene Host der Domain mit diesempurpose(siehedomains), z.B.https://${domain.main}.
userInputs selbst (z.B. ${HOST}, ${ADMIN_PASSWORD}) sind nach der Installation nicht mehr verfügbar und dürfen hier nicht referenziert werden. Wenn ein Eingabewert angezeigt werden soll, muss die Service-Umgebungsvariable referenziert werden, in die er fließt (z.B. ${postgres.env.POSTGRES_PASSWORD}); die öffentliche Domain wird über ${domain.<purpose>} referenziert.
help:
technicalDetails:
- key:
de: Hostname
en: Hostname
value: ${postgres.hostname}
- key:
de: Verbindungsstring
en: Connection string
value: postgresql://${postgres.env.POSTGRES_USER}:${postgres.env.POSTGRES_PASSWORD}@${postgres.hostname}/${postgres.env.POSTGRES_DB}
alerts:
- status: info
heading:
de: Admin-Benutzer manuell einrichten
en: Set up admin user manually
content:
de: Der Admin-Benutzer wird nicht automatisch angelegt. Richte ihn nach der Installation beim ersten Aufruf manuell ein.
en: The admin user is not created automatically. Set it up manually after installation on the first launch.Ein manifestbasierter Dev-Runner erzeugt lokale Eingabewerte und System-Secrets, rendert eine nur lokal verwendete Compose-Konfiguration und stellt die im Manifest definierten Domains über einen gemeinsamen Caddy-Reverse-Proxy mit HTTPS bereit. Die Originaldateien der Templates bleiben unverändert.
Voraussetzungen sind Docker mit Docker Compose sowie die für die Validierung benötigte Node.js- und pnpm-Version. Eine App wird mit folgendem Befehl gestartet:
pnpm app n8n upDanach ist sie unter https://n8n.localhost erreichbar. Templates mit mehreren Domains erhalten pro purpose eine Subdomain, beispielsweise https://eurooffice.nextcloud-euro-office.localhost. Mehrere Templates können gleichzeitig laufen, da nur der gemeinsame Proxy die Host-Ports 80 und 443 belegt.
Neben Caddy läuft ein globales Mailpit für lokale E-Mail-Tests. Die Web-Oberfläche ist unter https://mail.localhost erreichbar; Apps im gemeinsamen Dev-Netz erreichen SMTP unter ct-mail:1025 ohne TLS. Das eigenständige Mailpit-Template bleibt davon getrennt und läuft wie jedes andere Template unter https://mailpit.localhost.
Eine optionale docker-compose.dev.yml im Template-Ordner wird nach der generierten Compose-Datei als lokaler Override geladen und nicht produktiv verwendet.
Beim ersten Einsatz muss Caddys lokale Entwicklungs-CA einmalig als vertrauenswürdig installiert werden. Unter macOS landet sie ohne Administratorrechte im Keychain des angemeldeten Benutzers. Unter Debian/Ubuntu und Fedora/RHEL wird sie mit sudo in den systemweiten CA-Store aufgenommen:
pnpm app trustDie CA und alle Zertifikate bleiben in einem persistenten Docker-Volume erhalten. App-spezifische Werte werden mit restriktiven Dateirechten unter .dev/apps/<template>/values.json gespeichert, damit Passwörter und Encryption Keys über Neustarts hinweg stabil bleiben. .dev/ wird nicht versioniert.
Weitere Befehle:
pnpm app n8n logs # Logs verfolgen
pnpm app n8n down # Container stoppen, Daten behalten
pnpm app n8n reset # Container und App-Volumes löschen
pnpm app n8n config # generierte Compose-Datei nur prüfen
pnpm app n8n values # lokale Eingabewerte anzeigen
pnpm app status # laufende Compose-Projekte und URLs
pnpm app validate # Basis- und lokale Compose-Konfigurationen aller Templates prüfen
pnpm app proxy down # Caddy und Mailpit stoppen, CA und Zertifikate behalten
pnpm app untrust # lokale CA wieder aus dem Trust Store entfernenuntrust entfernt die CA unter macOS beziehungsweise Linux wieder aus dem verwendeten Trust Store. Anwendungen mit eigenem Zertifikatsspeicher, insbesondere einzelne Firefox- oder Snap-Installationen, können zusätzlich einen manuellen Import von .dev/proxy/root.crt benötigen.
Pflichtwerte werden für die lokale Entwicklung sinnvoll vorbelegt. Abweichende Werte lassen sich beim Start setzen und werden anschließend ebenfalls persistent verwendet:
pnpm app bugsink up \
--set ADMIN_EMAIL=me@example.test \
--set ADMIN_PASSWORD='MyLocalPassword123!'Mit --pull werden Images vor dem Start aktualisiert; --no-wait überspringt das Warten auf den laufenden beziehungsweise gesunden Containerzustand.
Component-Templates besitzen absichtlich keine öffentliche Domain. Für lokale Verbindungen kann ein Container-Port ausschließlich auf 127.0.0.1 veröffentlicht werden. Bei einem einzelnen Service genügt der Port; ein abweichender Host-Port oder der Service eines Templates mit mehreren Services kann explizit angegeben werden:
pnpm app postgresql up --publish 5432
pnpm app postgresql up --publish 15432:5432
pnpm app <template> up --publish <service>=15432:5432Vor dem Start prüft der Runner die Docker-Verbindung, die benötigten Host-Ports und doppelt registrierte Domains. Schlägt der Start fehl, zeigt er Containerstatus und die letzten Logs an und stellt die vorherige Caddy-Konfiguration wieder her. pnpm app status zeigt nur die vom Runner verwalteten ct-*-Projekte.
| Template | Beschreibung | |
|---|---|---|
| anythingllm | All-in-One KI-Desktop- und Server-Anwendung mit RAG | |
| apprise | Benachrichtigungen über eine API verteilen | |
| bentopdf | PDF-Werkzeugkasten direkt im Browser | |
| bugsink | Sentry-kompatibles Error-Tracking | |
| changedetection | Website-Änderungen automatisch erkennen | |
| chroma | Open-Source Vektordatenbank für KI-Anwendungen | |
| collabora | Online-Office-Suite für kollaboratives Arbeiten | |
| databasus | Datenbank-Backups automatisieren | |
| directus | Headless CMS und Datenplattform | |
| docmost | Kollaborative Wiki- und Dokumentationsplattform | |
| euro-office | Dokumente gemeinsam im Browser bearbeiten | |
| excalidraw | Whiteboard für Skizzen und Diagramme | |
| fider | Kundenfeedback sammeln und priorisieren | |
| gotenberg | Dokumente per API in PDF umwandeln | |
| healthchecks | Cronjobs und geplante Aufgaben überwachen | |
| hermes-agent | Selbstgehosteter KI-Agent mit dauerhaftem Gedächtnis | |
| immich | Foto- und Videoverwaltung | |
| infisical | Secrets zentral verwalten | |
| kaneo | Projektmanagement mit Board, Backlog und Zeiterfassung | |
| karakeep | Lesezeichen und Notizen sammeln | |
| kutt | Kurzlinks unter eigener Domain verwalten | |
| librechat | Selbstgehostete Chat-Oberfläche für KI-Modelle | |
| linkwarden | Lesezeichen sammeln und archivieren | |
| listmonk | Newsletter und Mailinglisten verwalten | |
| mailpit | E-Mail-Testserver für Entwicklung und Staging | |
| mariadb | Relationale Open-Source Datenbank | |
| meilisearch | Schnelle Volltextsuche für Anwendungen | |
| memos | Notizen und Gedanken schnell festhalten | |
| n8n | Automatisierung für deine Geschäftsprozesse | |
| nextcloud-euro-office | Cloud-Speicher und Zusammenarbeit mit Online-Office | |
| opensearch | Verteilte Such- und Analyse-Engine | |
| openwebui | Selbstgehostete Oberfläche für KI-Modelle | |
| paperclip | Steuerzentrale für Teams aus KI-Agenten | |
| paperless | Dokumentenmanagement mit OCR | |
| papra | Dokumente archivieren und verwalten | |
| password-pusher | Passwörter und Geheimnisse sicher teilen | |
| pgvector | PostgreSQL mit Vektorsuche für KI | |
| pocket-id | OIDC-Provider mit Passkeys | |
| postgresql | Leistungsstarke relationale Open-Source Datenbank | |
| qdrant | Hochperformante Vektordatenbank für KI-Anwendungen | |
| rallly | Terminabstimmung ohne Konto für Teilnehmende | |
| solr | Enterprise-Suchplattform | |
| stirling-pdf | Umfangreicher PDF-Werkzeugkasten | |
| umami | Datenschutzfreundliche Web-Analytics | |
| uptime-kuma | Monitoring und Statusseiten | |
| vaultwarden | Selbst gehosteter Passwort-Manager | |
| vikunja | Aufgaben und Projekte selbst verwalten | |
| weblate | Software gemeinsam übersetzen | |
| yopass | Geheimnisse sicher und verschlüsselt teilen |
- Ordner mit dem Template-Namen erstellen
docker-compose.ymlmit den benötigten Services anlegenmanifest.yamlerstellen — Felder und Beispiele in den Abschnitten oben, die verbindliche Struktur inmanifest.schema.jsonicon.svghinzufügen (Quelle: dashboard-icons, Apache-2.0)- Variablen in der
docker-compose.ymlüberuserInputsundsystemInputsim Manifest definieren - Optional Screenshots ergänzen (siehe Abschnitt screenshots)
- Konventionen in
AGENTS.mdbeachten (sichere Defaults, Zeitzone, Backups,help-Platzhalter-Regeln)
Vor dem Commit werden Pflichtdateien, Manifest und Screenshots per CI geprüft. Lokal:
Voraussetzung sind Node.js 24 und die in package.json festgelegte pnpm-Version.
pnpm install
pnpm test