From 2cccad81d85411c7466a958d7bc306aff70af012 Mon Sep 17 00:00:00 2001 From: va360labs Date: Wed, 12 Aug 2026 15:12:04 +0200 Subject: [PATCH] Add Didacta template Didacta is a fair-code self-hosted LMS. The blueprint ships the app image alongside a pgvector-enabled Postgres (the baseline migration runs CREATE EXTENSION "vector", so a plain postgres image fails on first boot) and Redis. A single domain is enough: the container runs the API on :4000 and the web on :3000, and Next.js rewrites /api/*, /healthz and /readyz to the internal API, so the domain only targets port 3000. --- blueprints/didacta/docker-compose.yml | 77 ++++++++++++++++++++++++++ blueprints/didacta/logo.svg | 4 ++ blueprints/didacta/meta.json | 13 +++++ blueprints/didacta/template.toml | 79 +++++++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 blueprints/didacta/docker-compose.yml create mode 100644 blueprints/didacta/logo.svg create mode 100644 blueprints/didacta/meta.json create mode 100644 blueprints/didacta/template.toml diff --git a/blueprints/didacta/docker-compose.yml b/blueprints/didacta/docker-compose.yml new file mode 100644 index 000000000..1f61e0650 --- /dev/null +++ b/blueprints/didacta/docker-compose.yml @@ -0,0 +1,77 @@ +# ============================================================================ +# Didacta Community — blueprint de Dokploy +# ---------------------------------------------------------------------------- +# Formato de `blueprints//docker-compose.yml` del repositorio +# Dokploy/templates. Reglas de esa casa que este fichero cumple: +# - Sin `ports:` (el proxy de Dokploy entra por la red interna). +# - Sin `container_name:` ni `networks:` (los gestiona Dokploy). +# - `restart: unless-stopped` en todos los servicios. +# - El nombre del servicio principal coincide con el id del blueprint. +# +# Las variables las inyecta Dokploy en un `.env` junto a este fichero a partir +# de `[config.env]` de template.toml — de ahí el `env_file` de cada servicio. +# +# UN SOLO DOMINIO: el contenedor corre API (:4000) y web (:3000) juntos, y +# Next.js reescribe `/api/*`, `/healthz` y `/readyz` al 4000 interno. El +# dominio de template.toml apunta al 3000 y no hace falta nada más. +# ============================================================================ + +services: + didacta: + image: ghcr.io/va360labs/didacta-community:0.0.1-alpha.107 + restart: unless-stopped + env_file: + - .env + expose: + - 3000 + volumes: + # Uploads de cursos, certificados y evidencias + la clave de cifrado + # autogenerada. Es el volumen que hay que respaldar. + - didacta-data:/app/data + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-fsS', 'http://127.0.0.1:4000/healthz'] + interval: 10s + timeout: 5s + retries: 30 + # El primer arranque aplica migraciones, RLS y grants antes de servir. + start_period: 120s + + postgres: + # pgvector NO es opcional: la migración baseline hace + # `CREATE EXTENSION "vector"` y falla contra un postgres:16 estándar. + image: pgvector/pgvector:pg16 + restart: unless-stopped + env_file: + - .env + volumes: + - didacta-postgres-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'] + interval: 5s + timeout: 5s + retries: 20 + + redis: + image: redis:7-alpine + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - didacta-redis-data:/data + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 3s + retries: 10 + +volumes: + didacta-data: + driver: local + didacta-postgres-data: + driver: local + didacta-redis-data: + driver: local diff --git a/blueprints/didacta/logo.svg b/blueprints/didacta/logo.svg new file mode 100644 index 000000000..609ace3f6 --- /dev/null +++ b/blueprints/didacta/logo.svg @@ -0,0 +1,4 @@ + + Didacta + + diff --git a/blueprints/didacta/meta.json b/blueprints/didacta/meta.json new file mode 100644 index 000000000..1c15f0e5b --- /dev/null +++ b/blueprints/didacta/meta.json @@ -0,0 +1,13 @@ +{ + "id": "didacta", + "name": "Didacta", + "version": "0.0.1-alpha.107", + "description": "Didacta is a fair-code, self-hosted learning platform (LMS) with multi-tenant isolation, a public course catalog, enrollment flows, gamification and a module marketplace.", + "logo": "logo.svg", + "links": { + "github": "https://github.com/va360labs/didacta-io", + "website": "https://didacta.io", + "docs": "https://docs.didacta.io" + }, + "tags": ["education", "lms", "elearning", "courses"] +} diff --git a/blueprints/didacta/template.toml b/blueprints/didacta/template.toml new file mode 100644 index 000000000..6920ef383 --- /dev/null +++ b/blueprints/didacta/template.toml @@ -0,0 +1,79 @@ +# ============================================================================ +# Didacta Community — configuración del blueprint de Dokploy +# ---------------------------------------------------------------------------- +# Dokploy resuelve los helpers de [variables], escribe [config.env] en un .env +# junto al docker-compose.yml y publica el dominio de [[config.domains]]. +# +# Helpers disponibles: ${domain}, ${password:N}, ${base64:N}, ${hash:N}, +# ${uuid}, ${randomPort}, ${email}, ${username}, ${timestamp}. +# ============================================================================ + +[variables] +main_domain = "${domain}" +# 64 caracteres sin símbolos: firma las sesiones. Rotarlo echa a todo el mundo. +auth_secret = "${password:64}" +# Token de un solo uso del alta inicial. Va en una URL, así que sin símbolos. +setup_token = "${password:32}" +# Va dentro de la cadena de conexión de Postgres: sin símbolos a propósito. +postgres_password = "${password:32}" + +[[config.domains]] +serviceName = "didacta" +port = 3000 +host = "${main_domain}" + +[config.env] +NODE_ENV = "production" +# Debe coincidir SIEMPRE con el tag de la imagen del docker-compose.yml. +DIDACTA_CORE_VERSION = "0.0.1-alpha.107" + +# Base absoluta de los enlaces que salen por correo (verificación de email, +# invitaciones, recordatorios). Sin esto los correos llevan enlaces rotos. +WEB_PUBLIC_URL = "https://${main_domain}" +WEB_PUBLIC_ALLOWED_HOSTS = "${main_domain}" + +# Credenciales del Postgres del propio blueprint. El servicio `postgres` las +# lee del mismo .env para inicializar el cluster. +POSTGRES_USER = "didacta" +POSTGRES_PASSWORD = "${postgres_password}" +POSTGRES_DB = "didacta" + +# Conexión de BOOTSTRAP (superusuario). Se usa solo para migraciones, RLS y +# grants — nunca para servir tráfico. El entrypoint deriva de aquí la conexión +# de runtime hacia el rol `didacta_app` (NOBYPASSRLS), que es lo que hace real +# el aislamiento entre tenants. Por eso NO se define DATABASE_URL. +ADMIN_DATABASE_URL = "postgresql://didacta:${postgres_password}@postgres:5432/didacta?schema=public" +REDIS_URL = "redis://redis:6379" + +AUTH_SECRET = "${auth_secret}" +# Fijarlo evita tener que buscar el token en los logs: el alta se hace en +# https:///setup?token=. +DIDACTA_SETUP_TOKEN = "${setup_token}" + +# Puertos INTERNOS fijos. No tocar: el dominio apunta al 3000 y el healthcheck +# al 4000. +API_PORT = "4000" +WEB_PORT = "3000" + +# Almacenamiento local sobre el volumen persistente `didacta-data`. Para S3 +# (AWS, Hetzner, MinIO…): STORAGE_DRIVER=s3 y rellenar las S3_*. +STORAGE_DRIVER = "local" +STORAGE_ROOT = "/app/data/storage" + +# SMTP: sin esto la instalación arranca igual, avisa por la interfaz y no puede +# mandar correos. Rellenar desde el panel de Dokploy después del despliegue. +SMTP_HOST = "" +SMTP_PORT = "587" +SMTP_USER = "" +SMTP_PASS = "" +SMTP_SECURE = "false" +SMTP_FROM = "" + +# Licencia Enterprise (opcional). Vacío = edición Community. +DIDACTA_LICENSE_KEY = "" + +# TENANT_SETTINGS_ENC_KEY se deja SIN definir a propósito: la app genera una +# clave hex de 32 bytes en el primer arranque y la persiste en el volumen +# `didacta-data`. Definirla con un helper que no produzca exactamente 64 +# caracteres hexadecimales rompería el cifrado de secretos (SSO, SMTP por +# tenant, Stripe) de forma silenciosa.