From 7d61fab2b75601653ab30d75f6520aca67550b57 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 31 Jan 2026 11:33:03 +0100 Subject: [PATCH] feat(stack): add Penpot design platform deployment Add self-hosted Penpot (Figma alternative) with: - Docker stack using shared PostgreSQL and Redis infrastructure - Authentik SSO integration (OIDC, password login disabled) - Three services: frontend, backend, exporter - Port 9001 for web UI, external via penpot.schweitz.net Co-Authored-By: Claude Opus 4.5 --- CONTAINERS.md | 48 ++++++++-- stacks/penpot.yml | 240 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 stacks/penpot.yml diff --git a/CONTAINERS.md b/CONTAINERS.md index 2a91051..1f25c58 100644 --- a/CONTAINERS.md +++ b/CONTAINERS.md @@ -1,7 +1,7 @@ # Container Reference - tower-of-joy Infrastructure -> **Last Updated:** 2026-01-22 -> **Total Services:** 31 containers across 20 stacks +> **Last Updated:** 2026-01-31 +> **Total Services:** 34 containers across 21 stacks > **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3 --- @@ -38,6 +38,7 @@ | **AMP (Game Server)** | 8080-8082 | https://amp.schweitz.net | Internet | No | - | ✅ Running | | **Home Assistant** | 8123 | https://housekeeping.schweitz.net | Internet | No | - | ✅ Running | | **Paperless-ngx** | 8091 | https://documents.schweitz.net | Internet | No | 8 | ✅ Running | +| **Penpot** | 9001 | https://penpot.schweitz.net | Internet (SSO) | No | 10 | ⏳ Pending Deploy | | **ClamAV** | 3310 | N/A (host service) | No | No | - | ✅ Running | | **AdGuard Home** | 53, 3053 | http://dns.schweitz.internal | LAN (DNS) | No | - | ✅ Running | | **Tatlock UI** | 9999 | https://home.schweitz.net | Internet | No | - | ✅ Running | @@ -53,6 +54,7 @@ - **amp.schweitz.net** → AMP Game Server - **housekeeping.schweitz.net** → Home Assistant - **documents.schweitz.net** → Paperless-ngx +- **penpot.schweitz.net** → Penpot (Protected by Authentik SSO) - **library.schweitz.net** → Wiki.js - **tatlock.schweitz.net** → Tatlock API (Protected by Authentik SSO) - **webui.schweitz.net** → Open WebUI (Protected by Authentik SSO) @@ -71,6 +73,7 @@ Internal domains provide LAN-accessible URLs without SSL or Authentik, ideal for | amp.schweitz.internal | localhost | 8080 | | housekeeping.schweitz.internal | localhost | 8123 | | documents.schweitz.internal | 192.168.86.149 | 8091 | +| penpot.schweitz.internal | localhost | 9001 | | git.schweitz.internal | localhost | 3002 | | library.schweitz.internal | localhost | 8088 | | tatlock.schweitz.internal | localhost | 8000 | @@ -158,8 +161,8 @@ PostgreSQL Shared is a centralized PostgreSQL 17 database server providing isola | **Resource Limits** | None | | **GPU Required** | No | | **Dependencies** | docker-dataplane network | -| **Databases** | `authentik` (Authentik SSO), `gitea` (Git hosting), `paperless` (Document management), `system_settings` (Central Tatlock settings), `sonarr` (TV management), `radarr` (Movie management), `prowlarr` (Indexer management), `postgres` (default/admin) | -| **Database Users** | `authentik_user`, `gitea_user`, `paperless_user`, `settings` (system_settings RW), `media_user` (sonarr/radarr/prowlarr), `postgres` (superuser) | +| **Databases** | `authentik` (Authentik SSO), `gitea` (Git hosting), `paperless` (Document management), `penpot` (Design platform), `system_settings` (Central Tatlock settings), `sonarr` (TV management), `radarr` (Movie management), `prowlarr` (Indexer management), `postgres` (default/admin) | +| **Database Users** | `authentik_user`, `gitea_user`, `paperless_user`, `penpot_user`, `settings` (system_settings RW), `media_user` (sonarr/radarr/prowlarr), `postgres` (superuser) | | **Health Check** | `pg_isready -U postgres` (30s interval) | | **Backup Strategy** | `/backups` volume for pg_dump exports | @@ -185,7 +188,7 @@ Redis Shared is a centralized Redis 7 key-value store providing cache, session s | **Resource Limits** | None | | **GPU Required** | No | | **Dependencies** | docker-dataplane network | -| **Database Allocation** | DB 0: Available, DB 1: Tatlock (memory), DB 2: Wiki.js, DB 3: Scheduler, DB 4: Library Desk, DB 5: SearXNG, DB 6: Tatlock (benchmarks), DB 7: Nextcloud, DB 8: Paperless, DB 9: Webber (sessions), DB 10-15: Available | +| **Database Allocation** | DB 0: Available, DB 1: Tatlock (memory), DB 2: Wiki.js, DB 3: Scheduler, DB 4: Library Desk, DB 5: SearXNG, DB 6: Tatlock (benchmarks), DB 7: Nextcloud, DB 8: Paperless, DB 9: Webber (sessions), DB 10: Penpot, DB 11-15: Available | | **Persistence** | AOF (Append-Only File) enabled for durability | | **Health Check** | `redis-cli ping` returns PONG (30s interval) | | **Connection String** | `redis://redis-shared:6379/0` (DB 0), `redis://redis-shared:6379/1` (DB 1), etc. | @@ -707,6 +710,35 @@ Paperless-ngx is a document management system that transforms physical documents --- +### Penpot + +Penpot is an open-source design and prototyping platform, serving as a self-hosted alternative to Figma with support for real-time collaboration, vector editing, prototyping, and design handoff. It provides a web-based interface for creating UI/UX designs, wireframes, and interactive prototypes with features like components, auto-layout, and multi-page documents. The service uses Authentik SSO for authentication (password login disabled), stores design assets on the SSD, and leverages shared PostgreSQL and Redis infrastructure for data persistence and real-time collaboration via WebSockets. + +| Property | Value | +|----------|-------| +| **Stack** | `penpot` | +| **Containers** | `penpot-frontend` (nginx), `penpot-backend` (Clojure API), `penpot-exporter` (PDF/SVG) | +| **Access URL (LAN)** | http://192.168.86.149:9001 | +| **Access URL (Public)** | https://penpot.schweitz.net | +| **Internal Domain** | http://penpot.schweitz.internal | +| **External Access** | Yes (via NPM reverse proxy with SSL + Authentik SSO) | +| **Port Mapping** | 9001:80 (HTTP) | +| **Network Mode** | Bridge (docker-dataplane) | +| **Restart Policy** | `unless-stopped` | +| **Volume Mounts** | `~/docker-data/penpot/assets:/opt/data/assets` (SSD - uploads, exports) | +| **Environment** | `PENPOT_PUBLIC_URI=https://penpot.schweitz.net`, `PENPOT_DATABASE_URI=postgresql://postgres-shared:5432/penpot`, `PENPOT_REDIS_URI=redis://redis-shared:6379/10`, `TZ=Europe/Amsterdam` | +| **Resource Limits** | Backend: 1GB memory, Exporter: 1GB memory | +| **GPU Required** | No | +| **Database** | PostgreSQL `penpot` on postgres-shared (user: penpot_user) | +| **Redis DB** | DB 10 (cache + WebSocket coordination) | +| **Dependencies** | PostgreSQL Shared, Redis Shared, Authentik (SSO), NPM (reverse proxy) | +| **Authentication** | Authentik OIDC only (password login disabled) | +| **Health Check** | Frontend: `curl http://localhost:80`, Backend: `curl http://localhost:6060/api/health` | +| **WebSocket Support** | Required (enabled via NPM for real-time collaboration) | +| **Features** | Vector editing, prototyping, components, auto-layout, multi-page, real-time collaboration, PDF/SVG export | + +--- + ### ClamAV ClamAV is an open-source antivirus engine running on the host OS, providing virus scanning capabilities for the entire server and accessible via TCP socket for container-based services like Paperless-ngx. It includes automatic virus definition updates via freshclam and can perform both on-demand and real-time scanning. Running on the host provides better security isolation than containerized scanning and allows scanning of the host filesystem directly. @@ -819,6 +851,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is | **Home Assistant** | https://housekeeping.schweitz.net | Yes | Smart home automation | | **Tatlock UI** | https://home.schweitz.net | Yes | Home lab dashboard | | **AMP** | https://amp.schweitz.net | Yes | Game server management | +| **Penpot** | https://penpot.schweitz.net | Yes (SSO) | Design & prototyping | | **Watchtower** | N/A (background) | N/A | Auto-updates | --- @@ -859,6 +892,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is | **Home Assistant** | `~/docker-data/home-assistant/config/` | N/A | Config: ~100MB, DB: ~50MB | | **Tatlock** | `~/docker-data/tatlock/logs/` | N/A | Logs: ~10MB | | **Webber** | `~/docker-data/webber/logs/`, `~/docker-data/webber/sandbox/` | N/A | Logs: ~10MB, Sandbox: variable | +| **Penpot** | `~/docker-data/penpot/assets/` | N/A | Assets: variable (uploads, exports) | | **Tatlock UI** | None (stateless) | N/A | ~0MB (static files in container) | **SSD Usage (docker-data):** ~6-11GB (configs, caches, databases) @@ -872,7 +906,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is | Network Name | Containers | Purpose | |--------------|------------|---------| -| **docker-dataplane** | Ollama, Open WebUI, Core API, Qdrant, PostgreSQL Shared, Redis Shared, Headscale, Nextcloud, Gitea, Samba, Watchtower, Tatlock, Webber, Tatlock UI, Home Assistant, Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd | Unified service mesh for all containerized applications | +| **docker-dataplane** | Ollama, Open WebUI, Core API, Qdrant, PostgreSQL Shared, Redis Shared, Headscale, Nextcloud, Gitea, Samba, Watchtower, Tatlock, Webber, Tatlock UI, Home Assistant, Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd, Penpot (frontend, backend, exporter) | Unified service mesh for all containerized applications | | **host** | Portainer, NPM, AMP (amp-ads + game containers) | Direct host port access for infrastructure and game servers | **Benefits of Consolidation**: @@ -909,4 +943,4 @@ redis-cli -h redis-shared # Redis connection --- -*Last Updated: 2026-01-09* +*Last Updated: 2026-01-31* diff --git a/stacks/penpot.yml b/stacks/penpot.yml new file mode 100644 index 0000000..9cddb90 --- /dev/null +++ b/stacks/penpot.yml @@ -0,0 +1,240 @@ +version: '3.8' + +# Penpot - Open Source Design Platform +# Purpose: Self-hosted Figma alternative for design and prototyping +# Port: 9001 (web UI) +# GPU: No +# External: penpot.schweitz.net +# Storage: SSD (assets), PostgreSQL shared, Redis shared (DB 10) + +services: + # ============================================ + # Penpot Frontend - Web UI (nginx) + # Port: 9001 (exposed) + # ============================================ + penpot-frontend: + image: penpotapp/frontend:latest + container_name: penpot-frontend + restart: unless-stopped + + ports: + - "9001:80" + + volumes: + - /home/jpmschweitzer/docker-data/penpot/assets:/opt/data/assets + + environment: + # Feature flags + PENPOT_FLAGS: enable-login-with-oidc disable-login-with-password disable-registration enable-oidc-registration + + # Backend connection (internal DNS) + PENPOT_BACKEND_URI: http://penpot-backend:6060 + PENPOT_EXPORTER_URI: http://penpot-exporter:6061 + + # Timezone + TZ: Europe/Amsterdam + + networks: + - docker-dataplane + + depends_on: + - penpot-backend + - penpot-exporter + + labels: + - "com.centurylinklabs.watchtower.enable=true" + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + # ============================================ + # Penpot Backend - API Server (Clojure) + # Internal only - connects to postgres-shared + redis-shared + # ============================================ + penpot-backend: + image: penpotapp/backend:latest + container_name: penpot-backend + restart: unless-stopped + + volumes: + - /home/jpmschweitzer/docker-data/penpot/assets:/opt/data/assets + + environment: + # Public URI (for callbacks and links) + PENPOT_PUBLIC_URI: https://penpot.schweitz.net + + # Feature flags (same as frontend) + PENPOT_FLAGS: enable-login-with-oidc disable-login-with-password disable-registration enable-oidc-registration enable-smtp + + # PostgreSQL (shared) + PENPOT_DATABASE_URI: postgresql://postgres-shared:5432/penpot + PENPOT_DATABASE_USERNAME: penpot_user + PENPOT_DATABASE_PASSWORD: YLmawuhcm0TLEMztkg/2Rzw+oAV6KPgHa3dZEYTkWr8= + + # Redis (shared, DB 10) + PENPOT_REDIS_URI: redis://redis-shared:6379/10 + + # Assets storage + PENPOT_ASSETS_STORAGE_BACKEND: assets-fs + PENPOT_STORAGE_ASSETS_FS_DIRECTORY: /opt/data/assets + + # Secret key (512-bit base64) + PENPOT_SECRET_KEY: +g+fSbAzBWVA2gE8ZnJUcpgq9L1BCqtuB+h9w73iF9qcgxhjMF1QU23XUjQ5ZdxpPmwoyuuQEm/frtKUV2M6pA== + + # Telemetry (disabled) + PENPOT_TELEMETRY_ENABLED: false + + # ============================================ + # Authentik OIDC Configuration + # ============================================ + PENPOT_OIDC_CLIENT_ID: ${PENPOT_OIDC_CLIENT_ID} + PENPOT_OIDC_CLIENT_SECRET: ${PENPOT_OIDC_CLIENT_SECRET} + PENPOT_OIDC_BASE_URI: https://auth.schweitz.net/application/o/penpot/ + PENPOT_OIDC_AUTH_URI: https://auth.schweitz.net/application/o/authorize/ + PENPOT_OIDC_TOKEN_URI: https://auth.schweitz.net/application/o/token/ + PENPOT_OIDC_USER_URI: https://auth.schweitz.net/application/o/userinfo/ + PENPOT_OIDC_SCOPES: openid profile email + PENPOT_OIDC_NAME_ATTR: name + PENPOT_OIDC_EMAIL_ATTR: email + + # SMTP Configuration (optional - for notifications) + # PENPOT_SMTP_DEFAULT_FROM: penpot@schweitz.net + # PENPOT_SMTP_DEFAULT_REPLY_TO: penpot@schweitz.net + # PENPOT_SMTP_HOST: smtp.example.com + # PENPOT_SMTP_PORT: 587 + # PENPOT_SMTP_USERNAME: username + # PENPOT_SMTP_PASSWORD: password + # PENPOT_SMTP_TLS: true + + # Timezone + TZ: Europe/Amsterdam + + networks: + - docker-dataplane + + labels: + - "com.centurylinklabs.watchtower.enable=true" + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:6060/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + deploy: + resources: + limits: + memory: 1G + reservations: + memory: 256M + + # ============================================ + # Penpot Exporter - PDF/SVG Export Worker + # Internal only + # ============================================ + penpot-exporter: + image: penpotapp/exporter:latest + container_name: penpot-exporter + restart: unless-stopped + + environment: + # Public URI (for rendering) + PENPOT_PUBLIC_URI: https://penpot.schweitz.net + + # Redis (shared, DB 10) + PENPOT_REDIS_URI: redis://redis-shared:6379/10 + + # Timezone + TZ: Europe/Amsterdam + + networks: + - docker-dataplane + + labels: + - "com.centurylinklabs.watchtower.enable=true" + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:6061"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + deploy: + resources: + limits: + memory: 1G + reservations: + memory: 256M + +networks: + docker-dataplane: + external: true + name: docker-dataplane + +# ============================================================================= +# DEPLOYMENT INSTRUCTIONS +# ============================================================================= +# +# Phase 1: Database Setup (DONE) +# Database 'penpot' and user 'penpot_user' created in postgres-shared +# +# Phase 2: Authentik OIDC Configuration +# 1. Go to https://auth.schweitz.net/if/admin/#/core/providers +# 2. Create new OAuth2/OpenID Provider: +# - Name: Penpot +# - Authorization flow: default-provider-authorization-implicit-consent +# - Client type: Confidential +# - Client ID: (auto-generated, copy to Portainer env vars) +# - Client Secret: (auto-generated, copy to Portainer env vars) +# - Redirect URIs/Origins: https://penpot.schweitz.net/api/auth/oauth/oidc/callback +# - Scopes: openid profile email +# - Signing Key: authentik Self-signed Certificate +# +# 3. Create Application: +# - Name: Penpot +# - Slug: penpot +# - Provider: Penpot (created above) +# - Launch URL: https://penpot.schweitz.net +# +# 4. Add environment variables in Portainer: +# PENPOT_OIDC_CLIENT_ID= +# PENPOT_OIDC_CLIENT_SECRET= +# +# Phase 3: Deploy Stack +# Deploy via Portainer with the OIDC environment variables +# +# Phase 4: NPM Proxy Configuration +# 1. Create new proxy host in NPM (http://192.168.86.149:81) +# 2. Domain: penpot.schweitz.net +# 3. Forward Host: 192.168.86.149 +# 4. Forward Port: 9001 +# 5. Enable: Block Common Exploits, Websockets Support +# 6. SSL: Request new Let's Encrypt certificate, Force SSL, HTTP/2 +# +# Phase 5: Verify Deployment +# 1. Access https://penpot.schweitz.net +# 2. Click "Login with OIDC" - should redirect to auth.schweitz.net +# 3. Login with Authentik credentials +# 4. Verify redirect back to Penpot with authenticated session +# 5. Create a test project to verify functionality +# +# ============================================================================= +# CREDENTIALS (stored in Portainer env vars) +# ============================================================================= +# PostgreSQL: +# Host: postgres-shared:5432 +# Database: penpot +# User: penpot_user +# Password: YLmawuhcm0TLEMztkg/2Rzw+oAV6KPgHa3dZEYTkWr8= +# +# Redis: +# Host: redis-shared:6379 +# Database: 10 +# +# Secret Key: +g+fSbAzBWVA2gE8ZnJUcpgq9L1BCqtuB+h9w73iF9qcgxhjMF1QU23XUjQ5ZdxpPmwoyuuQEm/frtKUV2M6pA==