- 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
20 KiB
Custom Templates - Vollständige Anleitung
📋 Inhaltsverzeichnis
- Überblick
- Template-System Architektur
- Anforderungen an Templates
- Neues Template erstellen
- Template-Beispiele
- Deployment
- Troubleshooting
- Best Practices
Überblick
Das Container Spawner System verwendet ein dynamisches Template-System, das beliebig viele User-Templates unterstützt:
- Automatische Erkennung:
install.shfindet und baut alleuser-template-*Verzeichnisse - Flexible Konfiguration: Templates werden in
.envdefiniert (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 Webservertemplate-02: Nginx Advanced - Nginx mit erweiterten Featurestemplate-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) |
.env → USER_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:
-
Port 8080 exposen (unprivileged, kein root)
EXPOSE 8080 -
Webserver auf Port 8080 laufen lassen
- Nginx:
listen 8080; - Node.js:
app.listen(8080) - Flask:
app.run(port=8080, host='0.0.0.0')
- Nginx:
-
Als unprivileged User laufen (Sicherheit)
USER nginx # oder node, www-data, etc. -
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 Tagdisplay_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:
- Findet alle
user-template-*Verzeichnisse - Baut Docker Images für jedes Template
- Startet Services neu
- 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
-
Unprivileged Users verwenden
# NICHT als root laufen USER nginx # oder node, www-data, appuser, etc. -
Minimale Base Images
# Bevorzuge alpine-Varianten FROM node:18-alpine # statt node:18 FROM python:3.11-slim # statt python:3.11 -
Keine Secrets im Image
# ❌ FALSCH ENV API_KEY=secret123 # ✅ RICHTIG - Via Runtime-Env # Secrets werden vom Spawner injiziert
Performance
-
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 -
Layer Caching optimieren
# Dependencies zuerst (ändern sich selten) COPY package*.json ./ RUN npm ci # Code danach (ändert sich oft) COPY . . -
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
-
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 -
README.md pro Template
# Template XYZ ## Was macht dieses Template? - Beschreibung - Features - Use Cases ## Konfiguration - Umgebungsvariablen - Volumes - Ports ## Entwicklung - Lokales Setup - Tests - Debugging -
Dokumentierte Umgebungsvariablen
# ENV-Variablen mit Defaults ENV NODE_ENV=production \ PORT=8080 \ LOG_LEVEL=info
Testing
-
Health Checks definieren
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 -
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!" -
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
.env→USER_TEMPLATE_IMAGESaktualisierttemplates.jsonmit 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.