spawner/docs/guides/custom-templates.md
XPS\Micro 92820c251c docs: add comprehensive custom templates guide
- Full guide for creating custom user templates
- Step-by-step instructions with requirements
- 3 complete examples: Python Flask, Hugo static site, Node.js with DB
- Deployment workflows for local and production
- Troubleshooting section and best practices
- Quick reference and checklist
2026-02-01 21:28:00 +01:00

20 KiB

Custom Templates - Vollständige Anleitung

📋 Inhaltsverzeichnis

  1. Überblick
  2. Template-System Architektur
  3. Anforderungen an Templates
  4. Neues Template erstellen
  5. Template-Beispiele
  6. Deployment
  7. Troubleshooting
  8. Best Practices

Überblick

Das Container Spawner System verwendet ein dynamisches Template-System, das beliebig viele User-Templates unterstützt:

  • Automatische Erkennung: install.sh findet und baut alle user-template-* Verzeichnisse
  • Flexible Konfiguration: Templates werden in .env definiert (semikolon-getrennt)
  • Metadaten-Driven: Display-Namen und Beschreibungen kommen aus templates.json
  • Multi-Container Support: Jeder User kann beliebig viele Container verschiedener Typen erstellen

Standardtemplates:

  • template-01: Nginx Basic - Einfacher statischer Webserver
  • template-02: Nginx Advanced - Nginx mit erweiterten Features
  • template-next: Next.js Production - React-App mit Shadcn/UI

Custom Templates hinzufügen: Beliebig viele eigene Templates erstellen (Python, Node.js, etc.)


Template-System Architektur

Wie das System funktioniert

1. install.sh durchsucht Verzeichnis
   ↓
2. Findet alle user-template-* Ordner
   ↓
3. Baut Docker Images automatisch
   ↓
4. Backend lädt Template-Liste aus .env
   ↓
5. Metadaten werden aus templates.json geladen
   ↓
6. Dashboard zeigt alle verfügbaren Templates
   ↓
7. User klickt "Erstellen" → Container spawnt

Dateien im System

Datei/Ordner Zweck
user-template-xyz/ Template-Verzeichnis (Dockerfile + Assets)
.envUSER_TEMPLATE_IMAGES Liste aller verfügbaren Images
templates.json Metadaten (Display-Name, Beschreibung)
config.py Lädt Templates dynamisch beim Start
container_manager.py Spawnt Container aus Templates

Template-Namen → Typ-Mapping

Das System extrahiert automatisch den Container-Typ aus dem Image-Namen:

# Beispiele:
'user-template-01:latest'     Typ: 'template-01'
'user-template-next:latest'   Typ: 'template-next'
'user-template-python:latest'  Typ: 'template-python'
'custom-nginx:v1.0'           Typ: 'custom-nginx'

Regel: Image-Name ohne user- Prefix und ohne Tag (:latest) = Container-Typ


Anforderungen an Templates

Pflicht-Anforderungen

Jedes Template MUSS:

  1. Port 8080 exposen (unprivileged, kein root)

    EXPOSE 8080
    
  2. Webserver auf Port 8080 laufen lassen

    • Nginx: listen 8080;
    • Node.js: app.listen(8080)
    • Flask: app.run(port=8080, host='0.0.0.0')
  3. Als unprivileged User laufen (Sicherheit)

    USER nginx  # oder node, www-data, etc.
    
  4. HTTP-Server bereitstellen (Traefik routet HTTP-Traffic)

Optionale Features

Templates KÖNNEN:

  • Datenbank-Container integrieren (via Docker Compose in Template)
  • Umgebungsvariablen nutzen (via ENV in Dockerfile)
  • Volume-Mounts für persistente Daten (via docker-compose.yml)
  • Build-Args für Konfiguration (z.B. ARG NODE_VERSION=18)

Was NICHT funktioniert

Templates KÖNNEN NICHT:

  • Andere Ports als 8080 nutzen (Traefik-Konfiguration)
  • Root-Rechte benötigen (Security Policy)
  • Direkten Zugriff auf Docker Socket (Isolation)
  • Andere Container spawnen (nur eigener Container)

Neues Template erstellen

Schritt 1: Verzeichnis erstellen

cd /path/to/spawner
mkdir user-template-myapp
cd user-template-myapp

Namenskonvention: user-template-<name>

  • <name>: Eindeutiger Identifier (lowercase, keine Sonderzeichen außer -)
  • Beispiele: user-template-python, user-template-django, user-template-flask

Schritt 2: Dockerfile erstellen

Basis-Vorlage (Nginx):

FROM nginx:alpine

# Expose Port 8080 (unprivileged)
EXPOSE 8080

# Kopiere statische Dateien
COPY index.html /usr/share/nginx/html/

# Nginx auf Port 8080 konfigurieren
RUN sed -i 's/listen       80;/listen       8080;/' /etc/nginx/conf.d/default.conf

# Run as unprivileged user
USER nginx

CMD ["nginx", "-g", "daemon off;"]

Erweiterte Vorlage (Node.js):

FROM node:18-alpine

# Working Directory
WORKDIR /app

# Install Dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy Application Code
COPY . .

# Expose Port 8080
EXPOSE 8080

# Run as unprivileged user
USER node

# Start Application
CMD ["node", "server.js"]

Wichtig: Port 8080 ist Pflicht!

Schritt 3: Template-Assets hinzufügen

Beispiel: Nginx mit statischer index.html

cat > index.html <<'EOF'
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>Mein Custom Template</title>
</head>
<body>
    <h1>Willkommen zu meinem Custom Container!</h1>
    <p>Dieser Container läuft auf Port 8080.</p>
</body>
</html>
EOF

Beispiel: Node.js mit server.js

cat > server.js <<'EOF'
const express = require('express');
const app = express();
const PORT = 8080;

app.get('/', (req, res) => {
    res.send('<h1>Node.js Template</h1><p>Läuft auf Port 8080</p>');
});

app.listen(PORT, '0.0.0.0', () => {
    console.log(`Server läuft auf Port ${PORT}`);
});
EOF

Schritt 4: .env aktualisieren

Öffne die .env Datei im Hauptverzeichnis:

nano .env

Füge dein Template zur USER_TEMPLATE_IMAGES Liste hinzu:

# Vorher:
USER_TEMPLATE_IMAGES="user-template-01:latest;user-template-02:latest;user-template-next:latest"

# Nachher:
USER_TEMPLATE_IMAGES="user-template-01:latest;user-template-02:latest;user-template-next:latest;user-template-myapp:latest"

Wichtig: Semikolon-getrennt, keine Leerzeichen, mit :latest Tag!

Schritt 5: templates.json aktualisieren

Öffne templates.json:

nano templates.json

Füge Metadaten für dein Template hinzu:

{
  "templates": [
    {
      "type": "template-01",
      "image": "user-template-01:latest",
      "display_name": "Nginx Basic",
      "description": "Einfacher Nginx-Server mit statischen Dateien"
    },
    {
      "type": "template-02",
      "image": "user-template-02:latest",
      "display_name": "Nginx Advanced",
      "description": "Nginx mit erweiterten Features"
    },
    {
      "type": "template-next",
      "image": "user-template-next:latest",
      "display_name": "Next.js Production",
      "description": "React-App mit Shadcn/UI"
    },
    {
      "type": "template-myapp",
      "image": "user-template-myapp:latest",
      "display_name": "Meine Custom App",
      "description": "Mein eigenes Template mit Node.js"
    }
  ]
}

Felder:

  • type: Muss mit extrahiertem Typ übereinstimmen (template-myapp)
  • image: Vollständiger Image-Name mit Tag
  • display_name: Name im Dashboard (beliebig)
  • description: Kurze Beschreibung für User

Schritt 6: Template lokal bauen

cd /path/to/spawner
docker build -t user-template-myapp:latest user-template-myapp/

Überprüfung:

docker images | grep user-template-myapp
# Expected: user-template-myapp   latest   abc123def456   5 seconds ago   150MB

Schritt 7: Template testen

Starte einen Test-Container:

docker run -d -p 8080:8080 --name test-myapp user-template-myapp:latest

Teste im Browser:

curl http://localhost:8080
# Expected: HTML-Output deines Templates

Cleanup:

docker stop test-myapp
docker rm test-myapp

Schritt 8: Änderungen committen

git add user-template-myapp/ .env templates.json
git commit -m "feat: add custom template-myapp"
git push

Template-Beispiele

Beispiel 1: Python Flask Template

Verzeichnisstruktur:

user-template-flask/
├── Dockerfile
├── requirements.txt
└── app.py

Dockerfile:

FROM python:3.11-slim

WORKDIR /app

# Install Dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy Application
COPY app.py .

# Expose Port 8080
EXPOSE 8080

# Run as unprivileged user
RUN useradd -m appuser
USER appuser

# Start Flask
CMD ["python", "app.py"]

requirements.txt:

Flask==3.0.0
gunicorn==21.2.0

app.py:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return '<h1>Flask Template</h1><p>Läuft auf Port 8080</p>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

In .env hinzufügen:

USER_TEMPLATE_IMAGES="...;user-template-flask:latest"

In templates.json hinzufügen:

{
  "type": "template-flask",
  "image": "user-template-flask:latest",
  "display_name": "Python Flask",
  "description": "Flask Web-Framework für Python"
}

Beispiel 2: Static Site Generator (Hugo)

Verzeichnisstruktur:

user-template-hugo/
├── Dockerfile
├── config.toml
└── content/
    └── _index.md

Dockerfile:

FROM klakegg/hugo:alpine AS builder

WORKDIR /src
COPY . .
RUN hugo --minify

FROM nginx:alpine

# Copy built site
COPY --from=builder /src/public /usr/share/nginx/html

# Nginx on Port 8080
RUN sed -i 's/listen       80;/listen       8080;/' /etc/nginx/conf.d/default.conf

EXPOSE 8080
USER nginx

CMD ["nginx", "-g", "daemon off;"]

config.toml:

baseURL = "/"
languageCode = "de-de"
title = "Hugo Template"
theme = "ananke"

Beispiel 3: Database-Backed Template (Node.js + PostgreSQL)

Hinweis: Für Multi-Container-Templates (mit DB) nutze docker-compose.yml innerhalb des Templates.

Verzeichnisstruktur:

user-template-node-db/
├── Dockerfile
├── docker-compose.yml
├── package.json
└── server.js

Dockerfile:

FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 8080
USER node

CMD ["node", "server.js"]

docker-compose.yml (wird vom Container genutzt):

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

Wichtig: Spawner erstellt nur den Main-Container. Für Multi-Container nutze einen Wrapper-Script oder starte via docker-compose im Container.


Deployment

Lokale Entwicklung

# 1. Template erstellen
mkdir user-template-xyz
cd user-template-xyz
# ... Dockerfile erstellen ...

# 2. .env und templates.json aktualisieren
nano ../.env
nano ../templates.json

# 3. Template bauen
docker build -t user-template-xyz:latest .

# 4. Spawner neu starten
cd ..
docker-compose restart spawner

# 5. Dashboard öffnen
# Template sollte jetzt sichtbar sein

Production Deployment (Server)

Schritt 1: Code committen

git add user-template-xyz/ .env templates.json
git commit -m "feat: add template-xyz"
git push

Schritt 2: Auf Server deployen

# SSH zum Server
ssh user@server

# Navigiere zum Spawner-Verzeichnis
cd /volume1/docker/spawner

# Pull neueste Änderungen
git pull

# Baue neues Template
docker build -t user-template-xyz:latest user-template-xyz/

# Kopiere aktualisierte Config-Dateien in Container
docker cp .env spawner:/app/.env
docker cp templates.json spawner:/app/templates.json

# Restart Spawner (lädt neue Konfiguration)
docker-compose restart spawner

Schritt 3: Verifikation

# Überprüfe ob Template geladen wurde
docker-compose logs spawner | grep "template-xyz"

# Teste via Debug-API
curl -H "X-Debug-Token: <token>" \
  "http://localhost:5000/api/admin/debug?action=info"

# Dashboard öffnen und neues Template prüfen

Automatisiertes Deployment (install.sh)

Das install.sh Script baut automatisch alle user-template-* Verzeichnisse:

# Auf Server:
cd /volume1/docker/spawner
git pull
bash install.sh

Was install.sh macht:

  1. Findet alle user-template-* Verzeichnisse
  2. Baut Docker Images für jedes Template
  3. Startet Services neu
  4. Templates erscheinen automatisch im Dashboard

Vorteil: Keine manuellen Docker-Builds nötig!


Troubleshooting

Problem: Template erscheint nicht im Dashboard

Symptom: Neues Template ist nicht in der Container-Liste sichtbar

Lösung:

# 1. Überprüfe .env im Container
docker exec spawner cat /app/.env | grep USER_TEMPLATE_IMAGES

# 2. Überprüfe templates.json im Container
docker exec spawner cat /app/templates.json

# 3. Überprüfe Backend-Logs
docker-compose logs spawner | grep -i template

# 4. Falls nicht aktualisiert, kopiere Dateien manuell:
docker cp .env spawner:/app/.env
docker cp templates.json spawner:/app/templates.json
docker-compose restart spawner

Problem: Container spawnt nicht / Fehler beim Start

Symptom: Klick auf "Erstellen" → Fehler "Container konnte nicht erstellt werden"

Lösung:

# 1. Überprüfe ob Image existiert
docker images | grep user-template-xyz

# 2. Falls nicht vorhanden, baue neu:
docker build -t user-template-xyz:latest user-template-xyz/

# 3. Teste Image manuell
docker run -d -p 8080:8080 --name test-xyz user-template-xyz:latest

# 4. Überprüfe Logs
docker logs test-xyz

# 5. Cleanup
docker stop test-xyz && docker rm test-xyz

# 6. Überprüfe Spawner-Logs
docker-compose logs spawner | tail -50

Problem: Port 8080 nicht erreichbar

Symptom: Container läuft, aber curl http://localhost:8080 gibt Timeout

Lösung:

# 1. Überprüfe ob Container wirklich auf 8080 hört
docker exec <container-id> netstat -tlnp | grep 8080

# 2. Überprüfe Dockerfile
cat user-template-xyz/Dockerfile | grep EXPOSE
# Expected: EXPOSE 8080

# 3. Überprüfe Webserver-Konfiguration
# Nginx: listen 8080;
# Node.js: app.listen(8080, '0.0.0.0')
# Flask: app.run(port=8080, host='0.0.0.0')

# 4. Rebuilde Template mit korrektem Port
docker build -t user-template-xyz:latest user-template-xyz/

Problem: Traefik routet nicht zum Container

Symptom: URL öffnet, aber zeigt 404 oder Timeout

Lösung:

# 1. Überprüfe Container-Labels
docker inspect user-<slug>-<type>-<id> | grep -A10 traefik

# 2. Überprüfe Traefik Dashboard
# http://<server>:8080 → HTTP Routers

# 3. Überprüfe StripPrefix Middleware
curl http://localhost:8080/api/http/middlewares | jq . | grep user

# 4. Überprüfe Traefik Logs
docker-compose logs traefik | grep user-<slug>

# 5. Starte Traefik neu
docker-compose restart traefik

Problem: Datei-Änderungen werden nicht übernommen

Symptom: Dockerfile geändert, aber Container nutzt alte Version

Lösung:

# 1. IMMER --no-cache beim Rebuild verwenden
docker build --no-cache -t user-template-xyz:latest user-template-xyz/

# 2. Alte Container entfernen
docker ps -a | grep user-template-xyz | awk '{print $1}' | xargs docker rm -f

# 3. Neue Container spawnen (via Dashboard oder API)

# 4. Überprüfe Image-Erstellungsdatum
docker images | grep user-template-xyz
# Should show recent timestamp

Best Practices

Sicherheit

  1. Unprivileged Users verwenden

    # NICHT als root laufen
    USER nginx  # oder node, www-data, appuser, etc.
    
  2. Minimale Base Images

    # Bevorzuge alpine-Varianten
    FROM node:18-alpine  # statt node:18
    FROM python:3.11-slim  # statt python:3.11
    
  3. Keine Secrets im Image

    # ❌ FALSCH
    ENV API_KEY=secret123
    
    # ✅ RICHTIG - Via Runtime-Env
    # Secrets werden vom Spawner injiziert
    

Performance

  1. Multi-Stage Builds nutzen

    # Build Stage
    FROM node:18-alpine AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build
    
    # Runtime Stage
    FROM nginx:alpine
    COPY --from=builder /app/dist /usr/share/nginx/html
    
  2. Layer Caching optimieren

    # Dependencies zuerst (ändern sich selten)
    COPY package*.json ./
    RUN npm ci
    
    # Code danach (ändert sich oft)
    COPY . .
    
  3. Image-Größe minimieren

    # Cleanup in einer RUN-Anweisung
    RUN apt-get update && \
        apt-get install -y pkg1 pkg2 && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/*
    

Wartbarkeit

  1. Versionierung nutzen

    # Statt :latest auch spezifische Versionen taggen
    docker build -t user-template-xyz:v1.0.0 .
    docker tag user-template-xyz:v1.0.0 user-template-xyz:latest
    
  2. README.md pro Template

    # Template XYZ
    
    ## Was macht dieses Template?
    - Beschreibung
    - Features
    - Use Cases
    
    ## Konfiguration
    - Umgebungsvariablen
    - Volumes
    - Ports
    
    ## Entwicklung
    - Lokales Setup
    - Tests
    - Debugging
    
  3. Dokumentierte Umgebungsvariablen

    # ENV-Variablen mit Defaults
    ENV NODE_ENV=production \
        PORT=8080 \
        LOG_LEVEL=info
    

Testing

  1. Health Checks definieren

    HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
      CMD curl -f http://localhost:8080/health || exit 1
    
  2. Test-Script erstellen

    #!/bin/bash
    # test-template.sh
    
    IMAGE="user-template-xyz:latest"
    CONTAINER="test-xyz"
    
    # Build
    docker build -t $IMAGE .
    
    # Run
    docker run -d -p 8080:8080 --name $CONTAINER $IMAGE
    
    # Wait
    sleep 5
    
    # Test
    curl -f http://localhost:8080 || exit 1
    
    # Cleanup
    docker stop $CONTAINER && docker rm $CONTAINER
    
    echo "✅ Template funktioniert!"
    
  3. Automatisierte Tests (optional)

    # CI/CD Pipeline (GitHub Actions, GitLab CI)
    # .github/workflows/test-templates.yml
    
    name: Test Templates
    on: [push]
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Test Template XYZ
            run: bash user-template-xyz/test-template.sh
    

Zusammenfassung

Schnell-Anleitung

# 1. Verzeichnis erstellen
mkdir user-template-myapp

# 2. Dockerfile erstellen (Port 8080, unprivileged user)
nano user-template-myapp/Dockerfile

# 3. Assets hinzufügen
cp index.html user-template-myapp/

# 4. .env aktualisieren
# USER_TEMPLATE_IMAGES="...;user-template-myapp:latest"

# 5. templates.json aktualisieren
# { "type": "template-myapp", "image": "...", ... }

# 6. Bauen & Testen
docker build -t user-template-myapp:latest user-template-myapp/
docker run -d -p 8080:8080 --name test user-template-myapp:latest
curl http://localhost:8080
docker stop test && docker rm test

# 7. Committen
git add user-template-myapp/ .env templates.json
git commit -m "feat: add template-myapp"
git push

# 8. Auf Server deployen
ssh user@server
cd /volume1/docker/spawner
git pull
docker build -t user-template-myapp:latest user-template-myapp/
docker cp .env spawner:/app/.env
docker cp templates.json spawner:/app/templates.json
docker-compose restart spawner

Checkliste

  • Template-Verzeichnis user-template-<name>/ erstellt
  • Dockerfile mit Port 8080 und unprivileged user
  • Template lokal gebaut und getestet
  • .envUSER_TEMPLATE_IMAGES aktualisiert
  • templates.json mit Metadaten erweitert
  • Änderungen committed und gepusht
  • Auf Server deployed (git pull + docker cp + restart)
  • Dashboard überprüft (Template sichtbar?)
  • Container erfolgreich erstellt und erreichbar

Support & Weitere Informationen

  • Hauptdokumentation: docs/install/DEPLOYMENT_GUIDE.md
  • Architektur: CLAUDE.md
  • Troubleshooting: docs/install/DEPLOYMENT_GUIDE.md#troubleshooting
  • API-Dokumentation: docs/api/ (falls vorhanden)

Bei Fragen: Issue auf GitHub/Gitea erstellen oder Admin kontaktieren.