Unofficial Penpot Manual Installation Guide for Debian 12
Penpot Manual Installation Guide for Debian 12
This guide attempts to walk through setting up a self-hosted Penpot instance manually on Debian 12, reverse-engineered from the official Docker configuration. This approach aims to give you full control over the installation process and a deeper understanding of Penpot's architecture.
Architecture Overview
Penpot has the architecture of a typical single page application (SPA). There is a frontend application, written in ClojureScript and using React framework, and served from a static web server. It talks to a backend application, that maintains data on a PostgreSQL database. The backend is written in Clojure, so the frontend and backend can share code and data. Then, the code is compiled into JVM bytecode and run in a JVM environment.
The core components you'll need to install and configure are:
- PostgreSQL 15 - Primary database
- Redis 7.2 - Used for websocket notifications and caching
- Penpot Backend - Clojure/JVM application
- Penpot Frontend - ClojureScript/React SPA
- Penpot Exporter - Service for generating exports
- Nginx - Web server and reverse proxy
- MailCatcher - SMTP service for development/testing
Prerequisites
System Requirements
- Debian 12 system with sudo access
- At least 4GB RAM (8GB+ recommended for your 64GB system)
- 10GB+ free disk space
- OpenJDK 17+ for running Clojure backend
- Node.js 18+ for building frontend assets
Initial System Setup
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install essential build tools and dependencies
sudo apt install -y \
wget curl git vim unzip \
build-essential \
postgresql-15 postgresql-client-15 postgresql-contrib-15 \
redis-server \
openjdk-17-jdk \
nodejs npm \
nginx \
python3 python3-pip \
libpq-dev \
supervisor
# Verify Java installation
java -version
javac -version
Phase 1: Database Setup
PostgreSQL Configuration
# Start and enable PostgreSQL
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Create Penpot database and user
sudo -u postgres psql <<EOF
CREATE DATABASE penpot;
CREATE USER penpot WITH PASSWORD 'your_secure_password_here';
GRANT ALL PRIVILEGES ON DATABASE penpot TO penpot;
ALTER USER penpot CREATEDB;
\q
EOF
# Test the connection
psql -h localhost -U penpot -d penpot -c "SELECT version();"
Redis Configuration
# Configure Redis
sudo vim /etc/redis/redis.conf
# Key settings to verify/modify:
# bind 127.0.0.1
# port 6379
# save 900 1
# Start and enable Redis
sudo systemctl start redis-server
sudo systemctl enable redis-server
# Test Redis connection
redis-cli ping
Phase 2: Application Directory Setup
# Create application directories
sudo mkdir -p /opt/penpot/{backend,frontend,exporter,assets,logs}
sudo mkdir -p /var/log/penpot
sudo useradd -r -s /bin/false -d /opt/penpot penpot
sudo chown -R penpot:penpot /opt/penpot
sudo chown -R penpot:penpot /var/log/penpot
Phase 3: Backend Installation
Based on the Docker image analysis, the backend requires a JVM environment with the compiled Clojure application.
Download and Extract Backend
# Create backend working directory
cd /opt/penpot/backend
# Since Penpot doesn't provide pre-built JARs, we need to build from source
# First, let's clone the repository
sudo -u penpot git clone https://github.com/penpot/penpot.git /opt/penpot/source
# Install Clojure CLI tools
curl -O https://download.clojure.org/install/linux-install-1.11.1.1413.sh
chmod +x linux-install-1.11.1.1413.sh
sudo ./linux-install-1.11.1.1413.sh
# Install Leiningen for backend build
curl -o /usr/local/bin/lein https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
# or maybe with sudo:
# sudo -s curl -o /usr/local/bin/lein https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
sudo chmod +x /usr/local/bin/lein
lein version
Backend Build Process
# Navigate to backend source
cd /opt/penpot/source/backend
# Build the backend application
# This will attempt to create a standalone JAR file
sudo -u penpot lein uberjar
# If successful, copy the JAR to the backend directory
sudo -u penpot cp target/app.jar /opt/penpot/backend/
# Create backend configuration file
sudo -u penpot tee /opt/penpot/backend/config.env <<EOF
# Database Configuration
PENPOT_DATABASE_URI=postgresql://localhost/penpot
PENPOT_DATABASE_USERNAME=penpot
PENPOT_DATABASE_PASSWORD=your_secure_password_here
# Redis Configuration
PENPOT_REDIS_URI=redis://localhost/0
# Application Configuration
PENPOT_PUBLIC_URI=http://localhost:3000
PENPOT_SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(64))")
# Asset Storage
PENPOT_ASSETS_STORAGE_BACKEND=assets-fs
PENPOT_STORAGE_ASSETS_FS_DIRECTORY=/opt/penpot/assets
# Feature Flags
PENPOT_FLAGS=disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-registration enable-login-with-password
# SMTP Configuration (using mailcatcher for development)
PENPOT_SMTP_DEFAULT_FROM=no-reply@localhost
PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@localhost
PENPOT_SMTP_HOST=localhost
PENPOT_SMTP_PORT=1025
PENPOT_SMTP_USERNAME=
PENPOT_SMTP_PASSWORD=
PENPOT_SMTP_TLS=false
PENPOT_SMTP_SSL=false
# Telemetry
PENPOT_TELEMETRY_ENABLED=true
# Server Configuration
PENPOT_HTTP_SERVER_HOST=0.0.0.0
PENPOT_HTTP_SERVER_PORT=6060
PENPOT_HTTP_SERVER_MAX_BODY_SIZE=31457280
PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=367001600
EOF
Phase 4: Frontend Installation
Frontend Build Process
# Navigate to frontend source
cd /opt/penpot/source/frontend
# Install Node.js dependencies
sudo -u penpot npm install
# Install shadow-cljs globally for ClojureScript compilation
sudo npm install -g shadow-cljs
# Build the frontend application
sudo -u penpot npm run build
# Copy built assets to frontend directory
sudo -u penpot cp -r resources/public/* /opt/penpot/frontend/
sudo -u penpot cp -r target/dist/* /opt/penpot/frontend/
Frontend Configuration
# Create frontend configuration
sudo -u penpot tee /opt/penpot/frontend/js/config.js <<EOF
window.penpotConfig = {
apiBaseURL: "http://localhost:6060",
publicURI: "http://localhost:3000",
flags: {
enableRegistration: true,
enableLoginWithPassword: true,
disableEmailVerification: true
}
};
EOF
Phase 5: Exporter Installation
The exporter service handles PDF and image generation.
# Navigate to exporter source
cd /opt/penpot/source/exporter
# The exporter is typically a Node.js application
sudo -u penpot npm install
# Build the exporter
sudo -u penpot npm run build
# Copy to exporter directory
sudo -u penpot cp -r dist/* /opt/penpot/exporter/
sudo -u penpot cp package.json /opt/penpot/exporter/
# Install production dependencies
cd /opt/penpot/exporter
sudo -u penpot npm install --production
# Create exporter configuration
sudo -u penpot tee /opt/penpot/exporter/config.env <<EOF
PENPOT_PUBLIC_URI=http://localhost:3000
PENPOT_REDIS_URI=redis://localhost/0
PENPOT_EXPORTER_HOST=0.0.0.0
PENPOT_EXPORTER_PORT=6061
EOF
Phase 6: Web Server Configuration
Nginx Setup
# Create Nginx configuration for Penpot
sudo tee /etc/nginx/sites-available/penpot <<EOF
server {
listen 3000;
server_name localhost;
# Max body size should match backend configuration
client_max_body_size 32M;
# Serve frontend static files
location / {
root /opt/penpot/frontend;
try_files \$uri \$uri/ /index.html;
# Add security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://localhost:6060/api/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Websocket support for notifications
location /ws/ {
proxy_pass http://localhost:6060/ws/;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
# Serve assets
location /assets/ {
alias /opt/penpot/assets/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Error and access logs
error_log /var/log/nginx/penpot_error.log;
access_log /var/log/nginx/penpot_access.log;
}
EOF
# Enable the site
sudo ln -s /etc/nginx/sites-available/penpot /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Phase 7: Service Management with Supervisor
Backend Service
sudo tee /etc/supervisor/conf.d/penpot-backend.conf <<EOF
[program:penpot-backend]
command=/usr/bin/java -jar /opt/penpot/backend/app.jar
directory=/opt/penpot/backend
user=penpot
environment=PATH="/usr/bin:/bin",$(cat /opt/penpot/backend/config.env | tr '\n' ',')
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/penpot/backend.log
stderr_logfile=/var/log/penpot/backend-error.log
EOF
Exporter Service
sudo tee /etc/supervisor/conf.d/penpot-exporter.conf <<EOF
[program:penpot-exporter]
command=/usr/bin/node index.js
directory=/opt/penpot/exporter
user=penpot
environment=PATH="/usr/bin:/bin",$(cat /opt/penpot/exporter/config.env | tr '\n' ',')
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/penpot/exporter.log
stderr_logfile=/var/log/penpot/exporter-error.log
EOF
Reload Supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start penpot-backend
sudo supervisorctl start penpot-exporter
Phase 8: Development SMTP Setup (MailCatcher)
# Install MailCatcher for development email testing
sudo gem install mailcatcher
# Create MailCatcher service
sudo tee /etc/systemd/system/mailcatcher.service <<EOF
[Unit]
Description=MailCatcher SMTP Server
After=network.target
[Service]
Type=simple
User=penpot
ExecStart=/usr/local/bin/mailcatcher --foreground --ip 127.0.0.1 --smtp-port 1025 --http-port 1080
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable mailcatcher
sudo systemctl start mailcatcher
Phase 9: Database Initialization
# The backend should automatically run migrations on first startup
# Monitor the backend logs to ensure successful initialization
sudo supervisorctl tail -f penpot-backend
# If manual migration is needed:
cd /opt/penpot/backend
sudo -u penpot java -cp app.jar clojure.main -m app.migrations.sql
Phase 10: Verification and Testing
Service Status Check
# Check all services are running
sudo systemctl status postgresql redis-server nginx mailcatcher
sudo supervisorctl status
# Check port bindings
sudo netstat -tlnp | grep -E ':(3000|6060|6061|1025|1080)'
# Test database connectivity
psql -h localhost -U penpot -d penpot -c "SELECT COUNT(*) FROM pg_tables;"
# Test Redis connectivity
redis-cli ping
Application Testing
- Open your browser and navigate to
http://localhost:3000
- Register a new account - emails will be captured by MailCatcher
- Check MailCatcher web interface at
http://localhost:1080
for verification emails - Test core functionality like creating projects, uploading assets, etc.
Troubleshooting
Common Issues
-
Build Failures: The biggest challenge will likely be building the Clojure/ClojureScript applications from source. The official Docker images use pre-built artifacts that aren't publicly available.
-
Missing Dependencies: You may need additional system libraries for the exporter service (Puppeteer/Chromium dependencies).
-
Memory Requirements: The build process is memory-intensive.
Alternative Approach: Extract from Docker
If building from source proves challenging, you can extract the application files from the official Docker images.
First make sure setup Docker on system:
https://docs.docker.com/engine/install/debian/
Add user to docker user group:
# Add your current user to the docker group
sudo usermod -aG docker $USER
# Apply the group changes immediately (without logout/login)
newgrp docker
# Pull official images
docker pull penpotapp/backend:latest
docker pull penpotapp/frontend:latest
docker pull penpotapp/exporter:latest
# Extract application files
docker create --name temp-backend penpotapp/backend:latest
docker cp temp-backend:/opt/app /opt/penpot/backend/
docker rm temp-backend
docker create --name temp-frontend penpotapp/frontend:latest
docker cp temp-frontend:/var/www/app /opt/penpot/frontend/
docker rm temp-frontend
docker create --name temp-exporter penpotapp/exporter:latest
docker cp temp-exporter:/opt/app /opt/penpot/exporter/
docker rm temp-exporter
Security Considerations for Production
The previous instructions were for setting this up on my Alienware m18 R2 laptop. But once I have that figured out, I am going to then try to set it up in a Debian 12 VM, such a my Xen Orchestra cloud environment and Xen VM instance running Debian 12. These are some considerations for that kind of setup:
- Generate secure passwords for all services
- Configure proper SSL/TLS certificates
- Set up firewall rules to restrict access
- Configure real SMTP service instead of MailCatcher
- Set up regular database backups
- Enable security flags like
secure-session-cookies
- Configure proper logging and monitoring